RAG · Knowledge Management · Productivity

The Private Personal Assistant

Build a local RAG pipeline that searches through your personal notes, journals, PDFs, and research — using plain English questions. Think "personal Google" for everything you've ever written or saved.

⏱ 25 min 📊 Beginner–Intermediate 📅 May 2026

What We're Building

A local search engine for your personal documents. You'll index your notes, journals, PDFs, and text files into a vector database, then query them with natural language: "What did I write about productivity systems last spring?" or "Find the journal entry where I talked about that conversation with Dad." Everything runs on your Pi. Nothing leaves your network.

Why This Matters

I have ten years of markdown notes, journal entries, project logs, and saved articles scattered across my filesystem. Some in ~/Documents, some in ~/notes, some buried in old backup drives. Traditional search — grep, filename matching, folder browsing — works fine when I know exactly what I'm looking for. It fails when I don't.

Semantic search is different. Instead of matching keywords, it matches meaning. A query like "ideas I had about garden automation" finds paragraphs about irrigation timers, soil moisture sensors, and Arduino-controlled watering — even if none of those exact words appear in the query. This is what RAG makes possible.

× Traditional Search

Search "garden automation" → only finds files containing those exact words. Misses notes about "watering system with moisture sensors" and "Arduino greenhouse controller."

✓ Semantic Search (RAG)

Search "garden automation" → finds everything conceptually related to automated gardening, regardless of the specific words used. Surfaces connections you forgot existed.

The Architecture

If you've already built the Offline-First AI Stack, you have all the components. We're going to customize them for personal document search:

~/Documents
~/notes
PDFs
Document Loader + Chunker
nomic-embed-text → ChromaDB
Query → Retrieve → LLM Answers

Step 1: Gather Your Documents

Before you index anything, take inventory of what you actually have. Most people underestimate their personal document corpus. Common sources:

  • Plain-text notes: Markdown, .txt, org-mode — anything text-based you've written
  • Journals and diaries: Daily logs, morning pages, project journals
  • PDF archives: Saved articles, research papers, manuals, e-books
  • Export dumps: Evernote .enex exports, Notion markdown exports, Apple Notes export
  • Code comments and READMEs: Project documentation you've written
  • Email archives: Exported .mbox files or plain-text email backups
Taking inventory
# How many text-based files do you have?
find ~/Documents ~/notes ~/Desktop -type f \( -name "*.txt" -o -name "*.md" -o -name "*.org" \) | wc -l
847

# Total size of your text corpus
find ~/Documents ~/notes -type f \( -name "*.txt" -o -name "*.md" \) -exec du -ch {} + | tail -1
12M total

# PDF count
find ~/Documents ~/Downloads -name "*.pdf" | wc -l
203
✓ You probably have more than you think

Don't Overthink This

You don't need to organize your files before indexing them. That's the whole point of semantic search — the system finds things regardless of where they're stored. Dump everything into a single directory if you want. The embeddings don't care about your folder structure.

Step 2: Install the Document Pipeline

Assuming you have the base stack (Ollama + ChromaDB) from the Offline-First guide, we need to add document loading capabilities for additional file types:

Installing document processing libraries
cd ~/ai-stack && source venv/bin/activate
pip install pymupdf pypdf2 python-docx beautifulsoup4
Collecting pymupdf...
Collecting pypdf2...
Collecting python-docx...
✓ Document processing libraries installed

Step 3: Build the Universal Document Loader

We need a loader that handles whatever you throw at it. Create universal_loader.py:

universal_loader.py
# Universal Document Loader for Personal RAG
# Handles: .txt, .md, .pdf, .docx, .html

import
os
from
pathlib
import
Path
from
typing
import
List, Tuple
def
load_text_file
(filepath: str) -> str:
"""Load a plain text or markdown file."""
with
open(filepath,
"r"
, encoding=
"utf-8"
, errors=
"ignore"
)
as
f:
return
f.read()
def
load_pdf
(filepath: str) -> str:
"""Extract text from a PDF."""
try
:
import
fitz
# pymupdf
doc = fitz.open(filepath) text =
"\n"
.join(page.get_text()
for
page
in
doc) doc.close()
return
text
except
ImportError:
from
PyPDF2
import
PdfReader reader = PdfReader(filepath)
return
"\n"
.join(page.extract_text()
or
""
for
page
in
reader.pages)
def
chunk_text
(text: str, chunk_size: int = 500, overlap: int = 50) -> List[str]:
"""Split text into overlapping chunks for embedding."""
words = text.split() chunks = []
for
i
in
range(0, len(words), chunk_size - overlap): chunk =
" "
.join(words[i:i + chunk_size])
if
len(chunk) > 50:
# Skip tiny tail chunks
chunks.append(chunk)
return
chunks
def
load_and_chunk_file
(filepath: str) -> Tuple[List[str], List[str]]:
"""Load a file and return (chunks, chunk_ids)."""
ext = Path(filepath).suffix.lower() filename = Path(filepath).name
if
ext
in
(
".txt"
,
".md"
,
".org"
,
".rst"
): text = load_text_file(filepath)
elif
ext ==
".pdf"
: text = load_pdf(filepath)
else
:
return
[], []
# Skip unsupported types

if
not
text.strip():
return
[], []
chunks = chunk_text(text) ids = [f"{filename}_chunk_{i}"
for
i
in
range(len(chunks))]
return
chunks, ids
def
index_directory
(directory: str, rag_engine, recursive: bool = True):
"""Walk a directory and index all supported files."""
pattern =
"**/*"
if
recursive
else
"*"
supported = {
".txt"
,
".md"
,
".org"
,
".pdf"
,
".rst"
}
total_chunks = 0
for
filepath
in
Path(directory).glob(pattern):
if
filepath.is_file()
and
filepath.suffix.lower()
in
supported: chunks, ids = load_and_chunk_file(str(filepath))
if
chunks: rag_engine.add_documents(chunks, ids) total_chunks += len(chunks) print(f" ✓ {filepath.name} ({len(chunks)} chunks)")
print(f"\nIndexed {total_chunks} chunks total.")
return
total_chunks

About the Chunk Overlap

The overlap=50 parameter means each chunk shares 50 words with the previous chunk. This prevents important ideas from being split across chunk boundaries. If a key sentence is the last 10 words of chunk 3 and the first 10 words of chunk 4, it'll be findable in both. You can adjust this — more overlap = better recall but more storage.

Step 4: Index Your Documents

Now the satisfying part — feeding your documents into the pipeline. Using the RAGEngine class from the Offline-First guide:

Indexing your personal documents
cd ~/ai-stack && source venv/bin/activate
python3
>>> from rag_engine import RAGEngine
>>> from universal_loader import index_directory

# Create your personal knowledge base
>>> personal_rag = RAGEngine(collection_name="personal_archive")

# Index your notes
>>> index_directory("/home/pi/notes", personal_rag)
✓ journal-2024.md (47 chunks)
✓ project-ideas.md (12 chunks)
✓ meeting-notes-q4.md (8 chunks)
...
Indexed 847 chunks total.

# Index PDF archives
>>> index_directory("/home/pi/Documents/pdfs", personal_rag)
✓ Your personal archive is now searchable

Indexing Takes Time — Plan Accordingly

Each document needs to be loaded, chunked, embedded (via Ollama), and stored in ChromaDB. On a Pi 5 without an NPU, expect roughly 1-3 seconds per chunk. A medium-sized personal archive (500-1000 chunks) might take 10-30 minutes. Run it in the background and let it work.

Step 5: Search Your Mind

With your documents indexed, here's what daily use looks like:

Querying your personal archive
python3
>>> from rag_engine import RAGEngine
>>> rag = RAGEngine(collection_name="personal_archive")

# Semantic search without generation
>>> results = rag.query("What did I write about solar panels?")
>>> for doc in results["documents"][0]:
... print(doc[:200])
... print("---")

# Full RAG: retrieve + generate answer
>>> answer = rag.generate_answer(
... "Summarize my thinking about solar panel ROI from my notes"
... )
>>> print(answer)
Based on your notes, your solar panel analysis focused on...

Real-World Use Cases

Daily journal search

"What was I thinking about last January?" → Finds journal entries from January 2025, even if they weren't about anything specific — the embedding captures the general tone, concerns, and themes of that period. This is eerily effective for self-reflection.

Project archaeology

"What was the original plan for the garden automation system?" → Surfaces notes from three years ago that you'd forgotten you wrote, including the sketch of the sensor layout and the rationale for choosing I²C over UART.

Cross-domain connections

"Have I ever connected ideas about game design to my work on nonprofit donor engagement?" → Finds a paragraph in a 2022 journal entry where you drew parallels between RPG quest structures and donor journey mapping. A connection you'd never have found with keyword search.

Research synthesis

"What do my saved articles say about forest garden design in temperate climates?" → Searches across 50+ saved PDFs and articles, pulls out relevant passages about species selection, guild planting, and microclimate management, and synthesizes them into a coherent summary.

Going Deeper: Personal Context for Daily AI Use

This is where the personal RAG pipeline becomes genuinely transformative. Instead of starting every AI conversation from scratch, you can feed the model context from your own life before asking questions.

Example: You're trying to decide whether to accept a job offer. You've been journaling about career satisfaction for years. Before asking the AI "Should I take this job?", you retrieve the 10 most relevant passages from your journal about career values, work-life balance preferences, and professional goals — then include those as context in your prompt.

The AI's response is now grounded in your actual thinking, not generic career advice. This is the difference between asking a stranger for advice and asking someone who has read your diary.

Ethical Note

The purpose of this tool is to help you see patterns in your own thinking — not to replace your judgment. The AI surfaces what you've already written; you decide what to do with it. I've found it most useful as a mirror: here's what you've been saying about this topic over time. What do you notice?

Keeping It Updated

An index is only useful if it stays current. I use a simple cron job to re-index new and modified files nightly:

Automated nightly re-indexing
# Create a maintenance script
cat > ~/ai-stack/reindex.sh << 'EOF'
#!/bin/bash
source /home/pi/ai-stack/venv/bin/activate
python3 -c "
from rag_engine import RAGEngine
from universal_loader import index_directory
rag = RAGEngine(collection_name='personal_archive')
index_directory('/home/pi/notes', rag)
index_directory('/home/pi/Documents/pdfs', rag)
print('Re-index complete')
"
EOF
chmod +x ~/ai-stack/reindex.sh

# Run at 2:07 AM daily
crontab -e
# Add this line:
7 2 * * * /home/pi/ai-stack/reindex.sh
✓ Personal archive stays current automatically

A Note on ChromaDB and Duplicates

ChromaDB doesn't automatically deduplicate by content. If you re-index the same file, you'll get duplicate entries. The simple approach above re-indexes everything nightly — for a small personal archive, this is fine (duplicates just mean stronger signal for those documents). For larger collections, add a last-modified check to the re-index script.

Model Recommendations

For personal document search, smaller models work surprisingly well because you're not asking them to generate world knowledge — just to find and synthesize what you've already written:

TASK RECOMMENDED MODEL WHY
Embeddings nomic-embed-text Fast, lightweight, excellent semantic quality. The only choice for embedding.
Simple Q&A llama3.2:3b Fast enough for interactive use. Good at extracting facts from context.
Synthesis & themes qwen2.5:7b Better at identifying patterns across multiple documents. Slower but more insightful.
Code + notes qwen2.5-coder:3b If your notes contain code snippets, this model understands both natural language and code.

Privacy: The Real Value

I want to be direct about why this matters. Your notes contain things you wouldn't post publicly: half-formed ideas, personal struggles, honest assessments of people and situations, creative work in progress. These are not things you should upload to a cloud AI service.

When you use ChatGPT or Claude with personal content, you're trusting a corporation with your inner life. Most people don't read the terms of service carefully enough to know what rights they're granting. Even if the company has good intentions today, data retention policies change, breaches happen, and training data is valuable.

A local RAG pipeline eliminates this trade-off entirely. You get the cognitive augmentation — the ability to search, synthesize, and explore your own thinking — without the privacy cost. Your journals stay on your hardware. Your ideas remain yours.

This isn't paranoia. It's architecture. The right tool for private data is a private tool.

Next Steps