Loci
Say "Hey Jarvis" and talk to a voice assistant that never leaves your Mac — no cloud APIs, no accounts, no subscription.
Role
Creator, solo project
Platform
macOS, Apple Silicon (8GB M1)
License
MIT, open source
Status
Working, on GitHub
What it does
Loci holds a conversation, remembers context across turns, and can act on your behalf — reading and writing files, running shell commands, opening apps, and searching the web — entirely through local models. Nothing leaves the machine: Ollama serves the LLM, faster-whisper and Piper handle speech in both directions, openWakeWord handles the always-on wake phrase, and a self-hosted SearXNG instance backs web search.
openWakeWord
Listens continuously for "Hey Jarvis" using a tiny, low-CPU model.
faster-whisper
Transcribes the command once triggered, auto-stopping when you stop talking.
Ollama + qwen2.5-coder
A local LLM with rolling conversation memory and the option to call a tool.
Piper TTS
Speaks the reply back through your speakers.
Built for 8GB, not scaled down to it
Loci was developed and tested on an 8GB M1 Mac, which shaped most of the real decisions: a 3B-parameter model (qwen2.5-coder:3b) instead of something larger that wouldn't fit in memory alongside two speech models and a wake-word listener running at once, and SearXNG installed natively rather than in Docker, to avoid the Desktop VM's overhead on an already tight machine. The wake-word listener alone idles at 8-15% CPU continuously — real battery cost for an always-on service, which is why a push-to-talk mode exists as a lower-power alternative.
Safety model
- read_file, open_application, web_search — no confirmation needed, nothing they do changes state
- write_file, type_text, run_shell_command — each prints exactly what it's about to do and requires a typed y/n, every time, with no "remember my choice" shortcut
- Every tool call, approved or not, is logged to tool_log.jsonl with a timestamp, the args, and a preview of the result
The 3B model doesn't populate Ollama's native tool-calling API — tested directly, it writes the JSON into a normal reply instead of the structured field Ollama expects — so the tool layer works around it with a prompt-based convention: the model emits a bare JSON object naming a tool, which gets parsed, executed, and fed back for a final natural-language reply.