0 KB
uploaded by design
The vault, index, vector store, and model provider all stay under the user's control.
1 binary
desktop distribution
A Tauri shell packages the app without Electron, Docker, or a separate Python service.
3 modes
agent depth
Fast, Normal, and Deep Research modes tune tool budgets, retrieval size, and graph traversal.

PKMA answering from a local vault, with the live telemetry panel tracking token usage, throughput, and the active model.
PKMA is a private, local-first chatbot for Markdown second brains. It is meant for the notes you do not want to upload anywhere: journals, private thoughts, drafts, research notes, and the rest of an Obsidian vault.
The app points at a folder of Markdown, builds a local SQLite index, talks to a local or user-selected model provider, and answers with citations to the notes it used. The point is not to make another generic document chatbot. The point is to make something safe enough to use next to a private journal.
- Live site: pkma.hegdeatri.com
- Source: hegdeatri/pkma-rs
What I built
PKMA ships as a desktop app with two main parts:
Rust core
A Tauri v2 backend starts a local Axum server on 127.0.0.1:8008. It owns vault indexing, SQLite access, provider calls, and the agent loop.
Next.js webview
A statically exported Next.js interface runs inside the desktop shell and talks to the Rust core over local HTTP and server-sent events.
Local retrieval
Each vault gets an isolated SQLite database with FTS5 keyword search and sqlite-vec semantic search, fused with reciprocal-rank fusion.
Bring-your-own model
Chat and embeddings work with Ollama, LM Studio, OpenRouter, llama.cpp, or any compatible OpenAI-style endpoint.
It looks like a normal chat app, but most of the work is in the fully local plumbing: parsing Markdown, keeping Obsidian structure intact, refreshing the index, and tying each answer back to a real note title.
How It Works
There is no hosted backend for the app. The desktop binary runs the UI and the local API, then stores each vault’s index on disk.
flowchart TB
user["Obsidian user"]
webview["Next.js webview"]
api["Axum local API 127.0.0.1:8008"]
agent["Rig agent loop"]
tools["vault tools"]
db["SQLite FTS5 + sqlite-vec"]
linkGraph["wikilink graph with petgraph"]
provider["Ollama / LM Studio / OpenAI-compatible provider"]
vault["Markdown vault"]
user --> webview
webview -->|HTTP| api
api -->|SSE| webview
api --> agent
agent --> tools
tools --> db
tools --> linkGraph
db --> vault
linkGraph --> vault
agent --> provider
That keeps the privacy story simple. With a local provider, PKMA can run without the network. With a hosted provider, the vault and index still stay off remote storage.
Finding Notes
The agent has to work with notes the way people actually write them: wikilinks, frontmatter, tags, aliases, daily notes, half-finished drafts, and personal naming habits.
The retrieval layer combines several signals:
- FTS5 keyword search for exact terms, note names, and phrases.
- Semantic vector search for ideas written in different words.
- Reciprocal-rank fusion to merge keyword and semantic results.
- Wikilink graph traversal so connected notes can surface even when embeddings miss them.
- Citation resolution so note pills show whether a title matched exactly, through FTS, through a fuzzy fallback, or not at all.
Indexing and Streaming
Indexing is incremental. A watcher notices file changes, note bodies are hashed with blake3, unchanged files are skipped, and files that fail parsing are quarantined so they do not keep cycling as “changed.” Switching vaults can cancel work in progress, which matters when one large vault is still embedding and another one opens.
Chat streams over SSE. Tokens, thinking steps, tool calls, and final messages are separate events, so the UI can feel live without adding a websocket service.
sequenceDiagram
participant UI as Next.js webview
participant API as Axum API
participant Agent as Agent loop
participant Tools as Vault tools
participant DB as SQLite index
participant LLM as Model provider
UI->>API: POST /chat
API->>Agent: start run
Agent->>Tools: vault_search
Tools->>DB: FTS + vector search
DB-->>Tools: ranked notes
Tools-->>Agent: cited context
Agent->>LLM: stream completion
LLM-->>API: tokens
API-->>UI: SSE events
What I learned
The hard part was not putting a chat box next to a model. It was making the local system trustworthy enough for private notes:
- SQLite needs clear read/write boundaries when the UI, indexer, and chat loop are all active.
- Obsidian vaults are messy in useful ways, so the parser and indexer need to tolerate imperfect Markdown.
- Citations are a product feature, not decoration; users need to know which note an answer came from.
- Local-first software still needs good cancellation, progress, and recovery states because large vaults make every long-running task visible.
PKMA is still evolving, but it is already close to what I wanted: a private research partner for my own notes, with local data and model choice built in.