A local, JARVIS-style personal assistant, developed by Thach Ngo (Thomas Ngo). Its mission is to run fully locally and boost working efficiency and productivity. A Python orchestrator drives a local, tool-calling Qwen3 model — via MLX on Apple Silicon (recommended, faster) or via Ollama everywhere else (the default) — which calls out to custom MCP servers over stdio for memory, calendar/reminders, weather, Notion task tracking, Claude Code delegation (planning/implementation/review), web search, IELTS grading/practice question generation, and running (Strava data + a local race tracker).
Most of this runs fully locally — LLM inference (MLX or Ollama, see below), SQLite memory, macOS Calendar/Reminders access via AppleScript. Several servers are explicit, accepted exceptions that reach the network:
- weather — plain HTTPS GET to open-meteo.com, no API key, no personal data (just coordinates or a city name).
- notion — talks to the Notion API with your integration token, to read and manage your task database.
- claude — the one server that reaches an LLM in the cloud (Anthropic's
API via the
claudeCLI). Every call runs with--permission-mode planfor read-only tasks oracceptEdits+ an explicit git push/commit denylist forimplement_from_plan— verified live, never commits or pushes regardless of what's asked. - web_search — queries a self-hosted SearXNG instance (see
searxng/). No API key or account of yours reaches a third party, but SearXNG itself still queries upstream engines (Google, Bing, etc.) over the network to gather results, as any metasearch aggregator does. Two alternatives were tried and rejected: Brave Search API requires a credit card on file even for its free tier, and Google's Custom Search API is closed to new signups as of 2025.
notion and claude degrade gracefully — if their .env secret isn't
set, that one server is skipped with a warning and everything else still
starts fine. web_search needs no secret, but does need the SearXNG
container actually running.
strava is fully local — it reads your own free personal data export
(activities.csv) rather than calling Strava's live API, since that now
costs $11.99/month for Standard-tier access (confirmed June 2026 policy
change). Not real-time; re-export periodically from Strava's account
settings to refresh. Its race tracker and find_races (web search for
candidate upcoming races, since Strava's API never had race data even
before the paywall) work with no Strava export at all.
Requesting the export itself needs a live logged-in Strava browser session,
so that part can't be automated — but everything after you download the
ZIP can be. A LaunchAgent (scripts/install_strava_automation.sh) runs
scripts/strava_automation.py daily, which:
- watches
~/Downloadsfor a Strava export ZIP (detected by checking foractivities.csvinside it, not by guessing Strava's exact filename) and auto-extracts it todata/strava_export/— no manual unzip/move step - creates a Reminders.app reminder every ~14 days nudging you to go re-request the export, since that step still needs you
Both halves are idempotent (tracked via marker files in data/), so the
daily run is a no-op on days there's nothing to do. Uninstall:
launchctl bootout gui/$(id -u)/com.thachngo.thaddeus.strava-automation
then remove the plist from ~/Library/LaunchAgents/.
The orchestrator talks to whichever local inference engine
MODEL_BACKEND in .env names — orchestrator/backend.py is the small
factory that switches between them, and every MCP tool/server works
identically either way since the backend only affects how the LLM itself
is served.
- macOS (recommended): MLX. Built natively for Metal/unified memory
rather than going through llama.cpp (what Ollama uses under the hood),
and confirmed empirically faster on this hardware — same Qwen3 model
family, thinking disabled on both sides:
- Plain question: 3.6s (MLX) vs ~8-9s (Ollama native API)
- Tool call: 1.05s warm (MLX, helped by prompt caching) vs 8.0s (Ollama)
- Multi-step Notion workflow (
list_connected_databases→get_database_schema→create_task): Qwen3-8B on MLX completed this correctly across 4/4 trials in 12.6-15.4s each; Qwen3-14B on MLX got the right result on its one trial but skipped the mandatory schema-check step (succeeded on a lucky guess) in 45.2s — the 8B build was both faster and more rule-compliant, which is why it's the default (MLX_MODELin.env) over the larger 14B. - Apple Silicon only — this is why it isn't the cross-platform default.
- Everything else (default): Ollama. Runs on any platform Ollama
supports; also the fallback if you're on a Mac but haven't set up MLX
yet.
MODEL_BACKENDdefaults toollamaif unset.
Things worth knowing about MLX before relying on it day to day:
- Qwen3 has the same thinking-mode latency trap on MLX as it does on
Ollama (on by default, burns the token budget on invisible reasoning) —
disabled the same way here (
MLX_THINK/chat_template_kwargs) as Ollama's native-APIthinkfield. - Both the 8B and 14B MLX builds carry Qwen3's native 40,960-token context
window with no cap applied by
mlx_lm.serveritself, unlike Ollama'sOLLAMA_NUM_CTX=16384here — worth an equivalent cap at themlx_lm.serverlayer if you're tight on memory, to avoid growing the KV-cache footprint (and eviction risk) unnecessarily. - All reliability testing so far used one fixed benchmark phrase ("Create a task on Notion for buying ice cream") — worth re-testing with varied phrasing/tasks before trusting the pattern beyond that.
- Set up your model backend:
- MLX (macOS, recommended):
pip install -e ".[mlx]", then in a separate terminal runmlx_lm.server --model mlx-community/Qwen3-8B-4bit --port 8082 --chat-template-args '{"enable_thinking":false}'(first run downloads the model, a few GB). SetMODEL_BACKEND=mlxin.env(step 4 below). - Ollama (default, any platform):
ollama serve(if not already running), then pull a tool-calling-capable build (seeOLLAMA_MODELin.envfor the exact tag in use, currentlyqwen3:14b). LeaveMODEL_BACKENDunset orollamain.env.
- MLX (macOS, recommended):
python3 -m venv .venv && source .venv/bin/activatepip install -e ".[dev]"cp .env.example .envand fill inMODEL_BACKEND(per step 1),WEATHER_DEFAULT_LAT/WEATHER_DEFAULT_LONpython scripts/sanity_check_sdk.py— confirms the installedmcpSDK's actual API surface before anything else is built against itpython scripts/init_db.pycd searxng && docker compose up -d && cd ..— starts the local search backend (one-time; it stays running across restarts)- Optional, for running tools beyond the race tracker: request a Strava
data export (account Settings > My Account > Download or Delete Your
Account > Request Your Archive), extract it to
data/strava_export/once it arrives by email, thenpython scripts/inspect_strava_export.pyto sanity-check the real column names/units against whatstrava_server.pyassumes python -m orchestrator.main