Weekend 2: when Plan A fails, Plan B works in ten minutes, and things get recursive.
In the previous article, I got the foundations working: site live, domains sorted, corpus structure in place. (If you’re new here, Article 1 explains why I’m building this.) Weekend 2 was supposed to be the payoff: make the corpus queryable, connect it to Claude via MCP, and finally stop copy-pasting context into every conversation.
The plan was Khoj. The plan didn’t survive contact with reality.
The obvious choice
Khoj looked perfect on paper. Open source, self-hosted, handles PDFs and images natively, has an API. The architecture doc from Weekend 1 listed it as the primary choice with Basic-Memory as a fallback. I’d done the research. This was going to be straightforward.
It wasn’t.
The self-hosted gap
To be clear: Khoj is a legitimate product. People pay for the cloud version, and it works. But this is a pattern I’ve seen before: when an open-source tool becomes commercial, the self-hosted path gets less love. The code is there, fully open source, but the tutorials lag, the configs drift, the “just works” experience moves to the paid tier.
What I hit:
Python version mismatch. My system Python is 3.14 (Homebrew stays current). Khoj’s dependencies don’t build on 3.14 — Pillow fails during compilation. Solution: install pyenv, build Python 3.12, create a dedicated virtual environment. Annoying but solvable.
PostgreSQL requirement. Khoj dropped SQLite support around version 1.40. The documentation still mentions SQLite; the code doesn’t. So now I need PostgreSQL running, plus pgvector for embeddings. For a personal knowledge tool indexing a few hundred files. The footprint was growing.
Migration bug. After all the setup, Khoj wouldn’t start. A database migration renamed ChatModelOptions to ChatModel, but the code still referenced the old name. Fresh install, immediate crash. I filed GitHub issues (#1244 for the migration, #1245 for the SQLite flag that’s documented but ignored).
Docker didn’t help either. Pivoted to Docker, thinking a known-good container image would bypass the dependency mess. It started. I uploaded 18 files. I ran a search query. Every query returned the same file. Different questions, same answer.
At this point I’d spent hours on a tool that demonstrably didn’t work for self-hosted. Time to try the backup.
Ten minutes to working search
Basic-Memory is simpler by design. No PostgreSQL, no Docker, no multi-container orchestration. SQLite for storage, sentence-transformers for embeddings, native MCP support. The trade-off: it only handles markdown, no built-in PDF or image extraction.
For my use case — a corpus of markdown files with the occasional PDF to be converted separately — that trade-off is fine.
curl -LsSf https://astral.sh/uv/install.sh | sh
uvx basic-memory init corpus ~/path/to/corpus
uvx basic-memory sync --project corpus
claude mcp add basic-memory -- uvx basic-memory mcp --project corpus
Four commands. Twenty-four files indexed. Restart Claude Code, and suddenly I can ask it to search my corpus mid-conversation. “What did I write about domain strategy?” pulls up the relevant notes without me copy-pasting anything. The thesis from Article 1 — retrieval that doesn’t feel like work — actually works now.
Beyond the bootstrap
Right now the corpus mostly contains notes about building the corpus itself. That’s a bit circular — of course semantic search seems marginal when everything is about the same project I’m already working on.
The real value shows up on the next project. When I start something new and can ask “what did I learn about MCP configuration?” or “how did I solve that Python version issue?” — that’s when accumulated context pays off. The corpus becomes useful precisely when it contains knowledge from other work.
But there’s a more immediate reason all this setup matters: claude.ai has no file access. No Read tool, no Glob, no Grep. If I want to work on this project in the browser — or on mobile — MCP is the only bridge to the corpus.
So the next question became: how to expose Basic-Memory to claude.ai?
I tried the obvious path first: run Basic-Memory in Docker, expose it via Cloudflare tunnel. It worked — technically. But laptop sleeps, Docker stops, tunnel drops. Fragile.
Then I discovered Basic-Memory has a cloud option. $14/month, handles sync and hosting, exposes a stable MCP endpoint. Connect it in claude.ai settings, authenticate, done. No Docker, no tunnel, no laptop dependency. For mobile work, turnkey beats DIY — at least until I have time to play with self-hosting again.
One surprise: MCP isn’t as unified as I’d assumed. Different Claude surfaces see different MCP servers:
| Surface | Local MCP | Cloud connector |
|---|---|---|
| Claude Code (terminal) | ✅ | ❌ |
| Claude Desktop chat | ✅ | ✅ |
| Claude Cowork | ✅ | ✅ |
| claude.ai web | ❌ | ✅ |
| Mobile | ❌ | ✅ |
So I need both: local MCP for Claude Code (fast, works offline), cloud connector for browser and mobile. But Basic-Memory v0.16+ hides the local mcp command when cloud mode is enabled — they assumed you’d only need one or the other. Had to pin to v0.15.0. Filed a feature request.
The lesson: even with standards like MCP, the integration details matter.
What I learned
The heavier tool isn’t always better. Khoj promises more features. Basic-Memory does less. Doing less means fewer moving parts, fewer ways to break.
File bugs, don’t just work around. I filed issues against Khoj (#1244, #1245) and a feature request against Basic-Memory (#517). Contributing back is part of using open source seriously.
Know when to pivot. The sunk cost fallacy is real. Walking away from hours of configuration work felt like waste (though honestly, Claude did most of that work — see the meta moment below). But debugging a broken tool was going to cost more hours with no guarantee of success.
The stack now
| Layer | Choice |
|---|---|
| Storage | Markdown files in Git |
| Search | Basic-Memory (SQLite locally, cloud for remote) |
| Integration | MCP stdio (Claude Code) + cloud endpoint (claude.ai) |
| Public site | Hugo + Cloudflare Pages |
Resource usage locally: one small Python process, one SQLite file. Daily maintenance: sync-memory runs a bidirectional sync between local and cloud. Run it once a day, corpus stays consistent everywhere.
Session management: the unexpected bonus
While debugging all of this, I stumbled into solving a different problem: how to run multiple Claude sessions in parallel without them stepping on each other.
Claude Desktop already does this automatically — each session gets its own git worktree, its own branch, its own isolated context. But Claude Code in the terminal doesn’t. I wanted the same isolation for terminal sessions: parallel work without conflicts, separate transcripts, clean merges.
The answer is git worktrees. A tool called ccswitch creates isolated branches for each Claude Code session. Each worktree gets its own Claude project directory, which means separate transcripts, separate context, separate history. I wrapped it in two helper scripts: start-session creates the worktree and launches Claude, close-session merges to main and cleans up.
The practical effect: I can have one session doing research while another writes code, and they don’t interfere. Each session has its own memory of what it was doing. When a session ends, its work merges back to main. It’s not quite multi-agent orchestration, but it’s a step toward being able to parallelize work across Claude instances.
The meta moment
Here’s the thing that still feels slightly surreal: this entire system was built by Claude.
Not “with Claude’s help” — Claude did the work. Claude wrote the configuration files, diagnosed the Khoj failures, figured out the Basic-Memory setup, debugged the MCP connectivity issues. I approved commands, provided direction, asked questions. But the actual implementation? That was Claude.
At one point I needed to test if the claude.ai connector actually worked. Claude — running in Claude Desktop — opened Chrome, navigated to claude.ai, configured the MCP connector, and ran a test search. Claude in the browser successfully queried the corpus. An AI configuring infrastructure so another instance of itself can access a knowledge base.
This article was also written by Claude, pulling from the corpus that Claude built. The corpus contains notes about the sessions where Claude did the building. It’s recursive in a way that makes my head spin if I think about it too long.
The practical takeaway: if you’re building AI-assisted workflows, the AI can build those workflows too. The documentation, the scripts, the troubleshooting — all of it. You just have to be willing to let it try.
What’s next
Weekend 3 is capture: making it easy to get things into the corpus. Browser extensions, email-to-corpus, maybe watch folders. The retrieval side works now; the input side needs attention.
If you’re building something similar: start with the simpler tool, and let Claude do the building. You can always add complexity later. You can’t easily remove it — and honestly, you probably won’t need to.
This is article 3 of “The Residue,” a series about building a personal knowledge corpus. Article 1 explains the problem. Article 2 covers the initial setup. Next: the capture problem.