Skip to main content
Glama
Reppin123

thought-search

by Reppin123

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.9MB

This 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.pdf if you type "fermi". You didn't remember "fermi". You remembered punch force and jaw.

  • An agent with shell access can ls, cat, and grep its way around — powerful, but blind. Every wrong folder it opens burns seconds and context window, and an unscoped mdfind or find / 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.

Related MCP server: FilePulse-CLI

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 status

The 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-mcp

Any 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/plocate or 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.mp3talk.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_line is chunk-start, not an exact span.

  • The eval is an A/B gate, not a benchmark. evals/find_file_ab.py scores hit@1 / found / latency against a bare-ls control 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.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

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