This repository was archived by the owner on Aug 2, 2026. It is now read-only.
Recall v0.3 — multi-source memory layer: daemon, connectors, CLI, MCP, docs - #4
Merged
Merged
Conversation
…ted repos - Exclude __pycache__, *.egg-info, mypy/pytest/ruff caches. - Ignore .env*, *.pem, *.key so secrets never sneak in. - Ignore ChromaDB persistence (./data/, ~/.vef/). - Ignore Raycast node_modules / dist / tarballs. - Ignore root-level JS scratch (Composio client etc.). - Ignore nested experimental git repos (moss/, moss1/). - Ignore generated analysis (graphify-out/) and tool caches (.cursor/, .context/, context/). Made-with: Cursor
…captioning
- embedder.py: pluggable provider factory keyed by VEF_EMBEDDING_PROVIDER.
* Ollama client for nomic-embed-text (default, zero network, zero rate
limits) — batches via /api/embeddings.
* Gemini Embedding 2 client with configurable VEF_EMBEDDING_DIMENSIONS
(128/256/512/768/1536) and automatic retry + backoff.
* OpenAI-compatible NIM client for self-hosted embedding endpoints.
- captioner.py: new module that runs Ollama vision models (moondream,
llava, minicpm-v, bakllava) and faster-whisper locally to produce
captions/transcripts for images, audio, and video before embedding.
detect_capabilities() probes what's installed and gracefully degrades.
- config.py: centralized settings loader that merges repo-level .env with
~/.vef/.env (the latter wins). Adds VEF_EMBEDDING_PROVIDER,
VEF_OLLAMA_EMBED_MODEL, VEF_EMBEDDING_DIMENSIONS, VEF_CAPTIONER_*,
VEF_PORT, VEF_DATA_DIR, VEF_CONCURRENCY knobs.
- .env.example: documents every knob with its default and valid values.
- pyproject.toml: adds ollama, httpx, google-genai, faster-whisper (opt),
canvasapi (opt) and defines 'setup'/'local-ai'/'canvas' extras.
Made-with: Cursor
- ingest.py:
* Route files to the correct embedder path (text vs. image/audio/video
via captioner, or binary fallback) based on MIME sniff, not extension.
* Deduplicate via SHA-256 content hash: if a file with identical bytes
is already in the collection under the same path, skip re-embedding.
* Report per-file progress to an in-memory channel consumed by
GET /progress so Raycast + CLI can stream status.
* Respect VEF_CONCURRENCY via a ThreadPoolExecutor; back off to 1 worker
when CPU is pinned or free RAM drops below 8 GB.
- watcher.py: new watchdog-based filesystem observer with a 2 s debounce
window. Tracks ~/.vef/watched_dirs.json and re-reads on SIGHUP so
adding/removing a folder doesn't require a daemon restart.
Made-with: Cursor
- reranker.py: new module implementing Reciprocal Rank Fusion over
candidate sets returned by the BM25 lexical index and the ChromaDB
vector store. Default k=60, configurable via VEF_RRF_K.
- search.py:
* Fire BM25 and vector queries in parallel (ThreadPoolExecutor), merge
via RRF, then trim to top_k.
* Accept source= filter (gmail, gcal, canvas, schoology, notion,
gdrive, calai, local) so Raycast/CLI can scope queries.
* Return source-aware metadata (subject+sender for email, course+title
for assignments, etc.) so result cards can render richly without a
second round-trip.
- store.py: add _throttled_warn() that dedupes identical Chroma warnings
to once per 60s; _safe_count() and search() both use it so a corrupted
collection can no longer spam ~/.vef/daemon.log.
Made-with: Cursor
All connectors share a common base.Connector contract with: - is_authenticated() — fast credential check before attempting any I/O. - sync() — incremental pull using a persisted cursor/token. - poll_interval_s — default cadence, can be overridden via env. Details: - gmail.py: OAuth2 Desktop flow, historyId-based incremental sync, pulls the last 6 months on first run, strips MIME quoted text. - gcal.py: OAuth2 Desktop flow, syncToken-based incremental sync, captures today/upcoming events with location + attendees. - gdrive.py: OAuth2 Desktop flow, changes.list pageToken, exports Google Docs/Sheets/Slides to text before embedding; skips binaries > 20 MB. - calai.py: API key, pulls upcoming bookings + attendee metadata. - canvas.py: access token via canvasapi, indexes assignments, announcements, modules, submissions + feedback comments. - schoology.py: OAuth1 (consumer key/secret), indexes assignments, updates, and inbox messages. - notion.py: integration token, indexes shared pages and database rows; respects 3 rps Notion rate limit. All connectors write their cursor to ~/.vef/credentials/<source>.json and emit a consistent source= metadata tag for per-source search filtering. Made-with: Cursor
…p, log rotation
Core HTTP surface on 127.0.0.1:19847 (configurable via VEF_PORT).
Endpoints:
GET /health — constant-time liveness probe ({\"status\":\"ok\"}).
GET /stats — document count + per-source breakdown
(moved off /health so the liveness check
can't stall on a backlogged ChromaDB).
POST /search — semantic query with optional source filter.
POST /ingest — enqueue files/dirs for indexing.
POST /sync — trigger connector sync now (idle-aware).
GET /sync-running — is a sync currently holding the lock?
GET /connector-status — per-connector last-sync + error state.
GET /progress — streaming ingest progress channel.
GET /sources — list of sources with credentials present.
* /watched-dirs — GET/POST/DELETE to manage watched folders.
POST /configure — persist .env changes to ~/.vef/.env (hot-reload).
Startup robustness (cmd_start):
- Pre-check PID file AND TCP port; probe /health to distinguish
\"live daemon with stale PID\" from \"nothing running\".
- Clean up stale PID files atomically.
- proc.poll() after spawn — never print \"Daemon started\" if the child
died before the first health probe.
- _poll_health() uses 2.0s httpx timeout per attempt (up from 500ms) to
tolerate transient CPU contention on slower Macs.
Operability:
- _configure_logging() installs a RotatingFileHandler on ~/.vef/daemon.log
(max 2 MB, 3 backups) so logs can't silently fill the disk.
- Connector scheduler pauses for 30s after any interactive search so
background work never competes with the user.
- VEF_CONNECTOR_SYNC_BUDGET_S bounds the global sync lock at 600s by
default to prevent a single runaway connector from blocking others.
Made-with: Cursor
- cli.py: new 'recall' binary (also aliased as vef-daemon for legacy
compatibility). Commands:
recall start — idempotent daemon spawn
recall stop — graceful SIGTERM with timeout
recall status — liveness + doc count from /stats
recall search <query> — terminal-friendly semantic search
with --source / --top-k flags
recall context <query> — top-5 with snippets, formatted for
pasting into an AI prompt
recall sync [<source>] — trigger connector sync now
recall index <path> — add a path to watched dirs and
kick off an immediate ingest
recall connect <source> — run the interactive auth flow
recall open-memory <query> — search and open top result in
macOS default handler
vef-daemon check-embed — end-to-end embedder smoke test
- mcp_server.py: MCP stdio server registering recall.search,
recall.context, recall.sync, recall.status, recall.index tools so
Claude Desktop / Cursor can query the same in-process index. Uses the
daemon's HTTP surface under the hood — no separate state.
Made-with: Cursor
Runner (src/lib/runner.ts):
- Typed error surface: DAEMON_UNREACHABLE, DAEMON_ERROR, NOT_CONFIGURED,
UNKNOWN — so every command can render a correct action-forward error.
- _pollHealth() uses a 2000ms per-attempt abort and 400ms poll interval,
tuned to survive transient macOS CPU spikes without false negatives.
- validateSetup() now hits /stats (not /health) to get the document
count, with a 5000ms budget — keeps the liveness probe cheap.
- autoStartDaemon() spawns the daemon through osascript, backoff-polls
/health for up to 15s, and surfaces the child's stderr in the toast on
failure so the user can see *why* startup failed.
- All requests go through a single http() helper with consistent abort
handling, JSON parsing, and structured error mapping.
New commands:
- src/manage.tsx — Manage Recall: status, watched folders
editor (add/remove), connector auth + sync
buttons, configure keys in-app.
- src/sync-status.tsx — live per-connector last-sync + in-progress
indicator via /connector-status polling.
- src/calendar-today.tsx — Today's events across GCal + cal.ai, grouped
by hour with location + attendees.
- src/email-search.tsx — Gmail-scoped search with subject + snippet
preview and 'Open in Gmail' action.
Refactors:
- src/search-memory.tsx — uses new shared ResultCard, adds source filter
dropdown, keyboard-driven action panel with
Copy/Open/Reveal shortcuts.
- src/open-memory.tsx — headless instant top-1 opener.
- src/components/ResultCard.tsx — single source of truth for result
rendering; per-source icon + accent color.
Config:
- package.json: registers the 5 commands, preference schema for Python
package path + binary.
- tsconfig.json: strict, moduleResolution=Bundler, ESNext.
Made-with: Cursor
setup_wizard.py (vef-setup entrypoint):
- rich + questionary UI with sectioned flow:
1. Pick embedding provider (Gemini / Ollama / NIM) with live model
pull for Ollama and API key validation for the other two.
2. Add watched folders (native file picker, expands ~).
3. Connect sources: Gmail → GCal → GDrive → cal.ai → Canvas →
Schoology → Notion. Each step is skippable and re-runnable.
4. Trigger the first full sync with a progress bar.
- Writes to ~/.vef/.env (not the repo-level .env) so pulling new code
never nukes local config. Existing keys are preserved on re-run.
- POSTs /configure after updates so the running daemon picks up new
settings without a restart.
install.sh:
- One-line installer: curl | bash.
- Detects Python ≥ 3.11, offers pyenv install if not present.
- pip install -e . with sensible extras.
- Drops recall / vef-daemon / recall-mcp on PATH via /usr/local/bin
symlinks.
- Runs vef-setup if ~/.vef/.env doesn't exist yet.
Made-with: Cursor
…workflow tests/ (new): - conftest.py: pytest fixtures for a throw-away ChromaDB collection, a mocked connector, and a running daemon bound to an ephemeral port. - test_daemon_http.py: asserts /health is constant-time (no count() call), /stats returns a numeric doc count, /search respects source filter, /sync is idle-aware, and /configure hot-reloads .env. - test_search.py: BM25 + vector RRF produces deterministic top-k for a seeded corpus; source= filter prunes correctly. - test_ingest.py: SHA-256 dedup skips re-embedding identical bytes at the same path; progress channel reports per-file status. - test_reranker.py: RRF formula is stable under rank ties; k parameter affects ordering only, never membership. - test_connectors.py: base.Connector contract is honored (is_authenticated, sync, poll_interval_s); cursor persistence roundtrips cleanly. - test_cli.py: recall start/status/stop lifecycle, recall search returns expected output, recall context formats snippets correctly. .github/workflows/moss-ci.yml: - ruff + mypy + pytest on every PR. - matrix over Python 3.11 / 3.12 on macos-latest. - caches pip + chromadb compile artifacts. Made-with: Cursor
…ting
README.md:
- Product-first intro with architecture hero image and latency figure.
- Quick start: one-line install → provider choice → first search.
- CLI walkthrough figure showing the 90% daily flow.
- Mermaid source diagram + per-connector table (auth / cadence /
credential file / CLI command).
- Raycast extension + MCP server sections with install snippets.
- Configuration reference (the 8 knobs that matter most).
- Resource-limit table (CPU backoff, RAM fallback, idle-aware sync).
- 'What's new in v0.3' section summarizing the stability + feature work.
docs/architecture.md:
- Process model: FastAPI + ThreadPoolExecutor + watchdog + connector
scheduler + ChromaDB + embedder + reranker in one process.
- Request path walkthroughs for /search, /ingest, connector sync.
- Endpoint map classified as fast (constant-time) vs slow (may touch
ChromaDB) — explains why /health and /stats are split.
- Startup robustness details and storage layout (~/.vef, VEF_DATA_DIR).
docs/cli-reference.md:
- Every recall / vef-daemon subcommand with flags, example output, and
the fig6_cli_walkthrough.png terminal screencap.
- Environment variable reference.
docs/daemon-api.md:
- curl examples for every endpoint with request/response JSON shapes.
- Error envelope convention + rate-limit / observability notes.
- Liveness vs. readiness distinction explained.
docs/troubleshooting.md:
- Concrete fixes for the failure modes users actually hit:
* 'Daemon process spawned but did not respond within 10s' → ChromaDB
corruption recovery steps.
* 'Daemon says running but Raycast shows 0 documents' → /stats vs
/health mismatch debug path.
* hnsw InternalError, EADDRINUSE, huge logs, stale PID, Ollama
connection refused, expiring Canvas/Schoology tokens, CPU pinned,
'Python binary not found' from Raycast.
* How to capture a diagnostic report.
docs/figures/:
- fig1_sources.png, fig2_categories.png — index distribution charts.
- fig3_latency.png — search latency benchmark.
- fig4_architecture.png — system architecture (regenerated, fixes the
stale 'Trayce' labels and overlapping text in the previous version).
- fig5_indexing_growth.png — corpus growth over time.
- fig6_cli_walkthrough.png — simulated terminal showing recall start /
status / search / sync / context.
AGENT_BUILD_GUIDE.md:
- How to add a new connector (subclass Connector, implement sync +
is_authenticated, register in __init__, add to setup wizard).
- How to add a new MCP tool.
- Local dev workflow tips.
docs/architecture/local-first-semantic-layer.md:
- Design notes for the experimental Rust + WASM runtime scaffold.
Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Recall v0.3 turns the project from a single-folder semantic-search demo into a local-first, multi-source memory layer that indexes files, Gmail, Google Calendar, Google Drive, cal.ai, Canvas, Schoology, and Notion into one ChromaDB collection and exposes it over a local daemon, a
recallCLI, a Raycast extension, and an MCP server.The branch is structured as 11 logical commits so each concern can be reviewed in isolation. See the change manifest below.
Before: a CLI that embedded one folder, hit Gemini on every query, and had no persistent process.
After: a background daemon indexing seven live sources, with sub-200 ms search, Ollama by default (zero network, zero rate limits), fully on-device captioning for images/audio/video, and a stable Raycast UX.
Highlights
Stability (the bug the user actually hit)
GET /healthis now constant-time — nochromadb.count()on the liveness path. Document counts moved toGET /stats.recall startpre-checks the PID file and the TCP port, probes/health, and usesproc.poll()to avoid false "Daemon started" messages when the child died during startup.~/.vef/daemon.logis rotated at 2 MB × 3 backups; repeated Chroma warnings are throttled to once per 60 s./healthprobe, 5 s budget forvalidateSetup.Features
Docs
Full rewrite with architecture, CLI reference, daemon API, and troubleshooting pages. Six figures (architecture diagram, CLI walkthrough, latency benchmark, index distribution, content categories, indexing growth).
Change manifest
Architecture
A single Python process hosts the HTTP surface, a `ThreadPoolExecutor` of ingest workers, a `watchdog` filesystem observer, and a connector scheduler. Everything goes through the embedder into one ChromaDB collection, so a single query ranks across every source.
Full write-up in `docs/architecture.md`.
CLI walkthrough
Test plan
Migration notes
```bash
recall stop
rm -rf ./data/chromadb ~/.vef/chroma
recall start
recall sync
```
/.vef/.env` (persistent). The repo-level `.env` is still read, but `/.vef/.env` overrides — so `git pull` never clobbers your keys.Made with Cursor