thought-search
thought-search
Find any file on your Mac by a vague memory of it. Spotlight for names, a
local semantic index for meaning, and results shaped for agents: a path:line
to start reading from, not a chunk dump. Nothing leaves the machine.
$ thought grep "the essay about calculating the punch force that could snap a jaw"
~/Documents/college/fermi-estimates-draft.pdf:118 (0.68) — assume the fist decelerates over ~2cm of jaw...
$ thought find "financial memo" --kind presentation
Found 2 in your work folders (newest first):
1. ~/Documents/Acme Financial - One Page Memo.pptx — 3d ago — 4.2MB
2. ~/Downloads/memo-draft-old.pptx — 2mo ago — 3.9MBThis is the file-search layer of Apprentice, a macOS AI apprentice, extracted as a standalone tool. It shipped to real users first; the code here is the shipped code, war-story comments included.
The problem
You know the file exists. You wrote it. You just can't produce the one thing every search interface demands: its name.
Two tools already live on your Mac, and each is broken alone:
Spotlight is fast and literal. It finds
fermi-estimates-draft.pdfif you type "fermi". You didn't remember "fermi". You remembered punch force and jaw.An agent with shell access can
ls,cat, andgrepits way around — powerful, but blind. Every wrong folder it opens burns seconds and context window, and an unscopedmdfindorfind /is a 30-60 second walk.
Semantic search should fix this, and in RAG form it half does: one query pulls content by meaning. But it hands back top-K chunks ripped out of their files — an answer fragment with no neighbors, no thread to pull, nowhere to stand.
The whole point is: you shouldn't have to pick.
The bet
Supermemory's SMFS work put numbers on the right design: let semantic search land on a path, then let the agent read, grep, and reason from that path with the tools it already has. Reach of semantic search, control of agentic search, one motion. (Their write-up is worth your time.)
SMFS mounts a cloud memory backend as a filesystem, so half their system is a sync engine: push queues, delta pulls, watermarks, FUSE/NFS mounts.
We took the other half of the idea and inverted the premise: your Mac is already the filesystem. The files are already local, already canonical, already yours. What's missing is not a mount — it's the semantic layer over what's there. So thought-search is the retrieval model without the cloud: no daemon, no sync, no account. An index in SQLite, a matrix in RAM, and two primitives.
Two primitives
find_file — the lexical leg. Name + kind + time + folder, straight off
the OS's own Spotlight index (mdfind, always scoped with -onlyin, never a
directory walk). Multi-word queries are tokenized so "financial memo" matches
Acme Financial - One Page Memo.pptx. Temporal queries use
kMDItemLastUsedDate — when you last opened the file, not when some
background process touched it — so "the deck I worked on yesterday" means what
you meant. On a miss it escalates: work folders → whole home directory →
external drives. It never dead-ends silently; it tells you where it looked.
semantic_grep — the semantic leg. File contents, chunked and embedded
locally (ONNX MiniLM, no PyTorch, ~15MB of runtime), searched by cosine over
an in-RAM matrix. A hit is path:line (score) — excerpt: a launch point. Open
the file, read around the line, follow the thread.
The router between them is not code — it's the model. find_file for
name/metadata references, semantic_grep for content references, and the
agent picks per query, exactly the way you reach for grep vs grep -r.
docs/AGENT_PROMPT.md is the battle-tested routing
doctrine to put in your agent's system prompt.
Install
pip install git+https://github.com/Reppin123/thought-search.git
thought index # crawl Downloads/Desktop/Documents, embed locally
thought grep "that doc about our pricing model"
thought find "contract" --since 'last week'
thought statusThe embedding model (~90MB, all-MiniLM-L6-v2, Apache-2.0) downloads once on first use. After that the tool runs fully offline. macOS only — the lexical leg is Spotlight.
For agents (MCP)
pip install "thought-search[mcp]"
claude mcp add thought-search -- thought-mcpAny MCP client works: {"mcpServers": {"thought-search": {"command": "thought-mcp"}}}.
Then wire docs/AGENT_PROMPT.md into your system
prompt — the tools are half the product; the routing doctrine is the other
half.
As a library
import asyncio
from thought_search import find_files, index_paths, search, start_background_index
index_paths(["~/Documents"]) # foreground crawl
start_background_index() # or: polite daemon-thread crawl
hits = search("unit economics assumptions") # [{path, score, snippet, start_line}]
res = asyncio.run(find_files("financial memo", kind="presentation"))What makes it fast (and keeps it fast)
Everything below was a measured failure first. The numbers come from the machine this shipped on — a real corpus that grew to 7,207 files / 280k chunks / 1.2GB of index — not a synthetic benchmark.
Content-hash freshness. Every file's text is hashed with a settings fingerprint (model, dims, chunking params). Identical bytes never re-embed: unchanged files skip on an mtime+size gate without even being read, and a moved/renamed/copied file reuses its vectors for free. Steady-state re-crawls cost approximately nothing.
Progressive commits. The index commits every 20 embedded files. The first version committed at the end of the crawl — 26 minutes in, the WAL held 71MB and every search said "index empty." Partial results should be searchable during the first crawl, and a killed process should keep its progress.
A polite background crawl. The first cut pegged ~4 cores for 26 minutes.
Now: os.nice(10) plus a per-embed throttle. An index that makes the laptop
fans spin is an index that gets uninstalled.
One matrix in RAM. Brute-force cosine is a single matmul — if the
vectors are already in memory. Reloading 388MB from SQLite per query cost
3.5-5.8s; cached, a query is ~40ms at 252k chunks. The cache key is the chunk
count, not SQLite's data_version — that pragma is per-connection, and
keying on it silently rebuilt the cache on every single query.
Best-chunk-per-file, lazily. Results are files, not chunks: argsort once, walk until top-k distinct files, early-exit. The cache holds no Python strings — at 252k rows, six 252k-element Python lists were a 73s warm-up cost by themselves. Snippets are fetched from SQLite only for the winners.
No type filter on semantic search. An inferred file-type filter is a
silent excluder — a wrong kind guess once dropped the exact PDF the query
was about, and the agent thrashed for 8 turns on a file the index had.
semantic_grep returns every type and shows the extension; choosing is the
caller's job.
bf16 storage: evaluated, rejected. Halving vector RAM sounds free until you notice numpy has no BLAS f16 matmul — you pay a 2-3s per-query upcast or you upcast at load and the RAM comes back. Honest dead ends stay in the comments so nobody re-walks them.
What it deliberately doesn't do
No cloud, no sync engine, no account. SMFS needs push queues and delta pulls because it mirrors to a backend. There is no backend here. Your files never leave your machine; neither do their embeddings.
No daemon, no FUSE/NFS mount. It's a library, a CLI, and an MCP server. The OS filesystem is already mounted.
No hardcoded query planner. The model decomposes "the pdf about X from last week" into parameters inline. Speed comes from the indexes; smarts stay in the model.
Status and honest limitations
macOS only. The lexical leg is Spotlight. A Linux leg would need
locate/plocateor similar.Index freshness is crawl-based (session start /
thought index), not a live file-watcher. Watcher is the natural next step.Text, code, PDF, and docx are indexed today. Images/audio/video want transcription siblings (
talk.mp3→talk.mp3.transcript.md) — designed, not built.MiniLM is the floor. 384-dim, 256-token context; it lands on the right files but scores are modest. The embedder is a deliberate seam — swapping in a longer-context model is a two-constant change plus a re-index.
start_lineis chunk-start, not an exact span.The eval is an A/B gate, not a benchmark.
evals/find_file_ab.pyscores hit@1 / found / latency against a bare-lscontrol on your files; there is no xAFS-style public corpus here yet. Numbers above are one real machine, disclosed as such.
Credits
The retrieval philosophy — semantic search as a navigation aid that lands on paths — is SMFS's (technical report), here reimplemented local-first. Embeddings by all-MiniLM-L6-v2 over ONNX Runtime. Extracted from Apprentice.
MIT.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Reppin123/thought-search'
If you have feedback or need assistance with the MCP directory API, please join our Discord server