Back to experience✨ Open source

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.