KevsRobots Learning Platform
85% Percent Complete
By Kevin McAleer, 7 Minutes
Page last updated August 23, 2026

An index is only useful if it matches your notes. Right now you have to remember to run amv index, and you will not.
Because indexing is incremental, keeping up to date is cheap - a no-change run on a big vault takes well under a second. That opens up options that would be silly if a rebuild took a minute.
| Approach | Latency | Complexity | Good for |
|---|---|---|---|
| Run it before you ask | Seconds | None | Everyone, honestly |
| Scheduled job | Up to an hour | Low | Vaults you edit all day |
| File watcher | Instant | Medium | If instant genuinely matters |
Start at the top. Move down only when the one above stops being good enough.
The simplest thing that works. Add a flag:
# vault_rag/cli.py (in build_parser - give ask its own subparser rather than
# building it in the shared loop, so only ask gets this flag)
ask_parser.add_argument("--fresh", action="store_true",
help="Re-index before answering")
# vault_rag/cli.py (add these lines to the top of cmd_ask)
def cmd_ask(args) -> int:
from .answer import check_citations, stream_answer
if args.fresh:
index_vault(VAULT_PATH, args.db, verbose=False)
...
On a vault where nothing changed, index_vault reads the hashes, compares, and returns. You will not notice it happen.
If you find yourself passing --fresh every time, just make it the default and add --stale for the rare case where you want to skip it.
For a vault you edit throughout the day, run it on a timer. On macOS or Linux, cron is the least effort:
crontab -e
# Re-index the vault every 30 minutes
*/30 * * * * /Users/kev/ask-my-vault/.venv/bin/amv index --quiet >> /tmp/amv.log 2>&1
Two things that catch people out with cron:
Use the absolute path to the venvβs amv. Cron does not run your shell profile, so your virtualenv is not active and amv is not on the PATH.
Cron has almost no environment. VAULT_PATH will not be set, so either put the default in config.py or set it in the crontab:
VAULT_PATH=/Users/kev/Obsidian/MyVault
*/30 * * * * /Users/kev/ask-my-vault/.venv/bin/amv index --quiet >> /tmp/amv.log 2>&1
Redirecting to a log file is worth doing. A cron job that fails silently is a cron job you will discover three weeks later when you wonder why the answers are stale.
On a Raspberry Pi that runs all the time, this is the right answer - index every half hour and forget about it.
If you want the index updated the moment you save a note, watch the filesystem. This needs one more dependency:
pip install watchdog
# vault_rag/watch.py
"""Watch the vault and re-index when notes change."""
import time
from pathlib import Path
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from .config import DB_PATH, VAULT_PATH
from .index import index_vault
DEBOUNCE_SECONDS = 5.0
class VaultHandler(FileSystemEventHandler):
"""Note when a markdown file changes; the main loop does the work."""
def __init__(self):
self.dirty_since: float | None = None
def on_any_event(self, event):
if event.is_directory:
return
if not str(event.src_path).endswith(".md"):
return
self.dirty_since = time.time()
def watch(vault_path: Path = VAULT_PATH, db_path: Path = DB_PATH):
"""Re-index a few seconds after the last change settles."""
handler = VaultHandler()
observer = Observer()
observer.schedule(handler, str(Path(vault_path).expanduser()), recursive=True)
observer.start()
print(f"Watching {vault_path}. Ctrl-C to stop.")
try:
while True:
time.sleep(1)
if handler.dirty_since is None:
continue
if time.time() - handler.dirty_since < DEBOUNCE_SECONDS:
continue
handler.dirty_since = None
stats = index_vault(vault_path, db_path, verbose=False)
if stats.added or stats.updated or stats.removed:
print(f" {stats.summary()}")
except KeyboardInterrupt:
observer.stop()
observer.join()
The debounce is the important part. Obsidian saves on a timer while you type, so a single paragraph produces a dozen write events. Without debouncing you would re-index a dozen times. Waiting five seconds after the last change means you index once, when you pause.
We do not track which file changed. We could, but incremental indexing already figures that out from the hashes, and a βsomething changed, go lookβ signal is far simpler than maintaining a queue of paths. Let each layer do its own job.
Only printing when something happened keeps the terminal quiet. A watcher that logs β0 changedβ every five seconds is a watcher you will kill.
Add it as a command:
# vault_rag/cli.py (add below cmd_stats, and add a `watch` subparser in build_parser)
def cmd_watch(args) -> int:
from .watch import watch
watch(args.vault, args.db)
return 0
On macOS, a launch agent at ~/Library/LaunchAgents/com.kevsrobots.amv.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!-- ~/Library/LaunchAgents/com.kevsrobots.amv.plist -->
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.kevsrobots.amv</string>
<key>ProgramArguments</key>
<array>
<string>/Users/kev/ask-my-vault/.venv/bin/amv</string>
<string>watch</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardErrorPath</key>
<string>/tmp/amv.err</string>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/com.kevsrobots.amv.plist
On Linux, a systemd user unit at ~/.config/systemd/user/amv.service:
# ~/.config/systemd/user/amv.service
[Unit]
Description=Ask My Vault indexer
[Service]
ExecStart=/home/kev/ask-my-vault/.venv/bin/amv watch
Restart=always
Environment=VAULT_PATH=/home/kev/Obsidian/MyVault
[Install]
WantedBy=default.target
systemctl --user enable --now amv
If your vault syncs across devices with Obsidian Sync, iCloud or Dropbox, the watcher fires when a sync writes a note, not just when you do. That is correct behaviour - the note really did change - but it means the index updates from edits made on your phone too, which is rather nice.
The one thing to avoid is putting the Chroma database inside the synced vault. It is a binary SQLite file plus index data; syncing it between machines will corrupt it, and re-indexing locally is fast anyway. Keep it in ~/.ask-my-vault/, as config.py already does.
amv index on your vault with nothing changed. Under a second? Then --fresh on every ask costs you nothing./tmp/amv.log the next day. Did it run? Did it find anything?DEBOUNCE_SECONDS to how fast you type.--fresh flag to search as well as ask. Do you actually want it there? Searching is often something you do while editing./tmp/amv.log. If it is empty, cron is not firing at all - on macOS, grant cron Full Disk Access in System Settings.Why: Recent macOS versions block cron from reading Documents and iCloud folders until you allow it explicitly.
Why: We already filter to .md, but a plugin writing .md cache files will still trigger it. Add its folder to SKIP_DIRS and filter the event path too.
OSError: inotify watch limit reached on Linux.sudo sysctl fs.inotify.max_user_watches=524288.Why: Recursive watching costs one inotify watch per directory, and the default limit is low for a big vault.
You can use the arrows β β on your keyboard to navigate between lessons.
Comments