The Fully Local Home Voice Assistant, a private voice layer for your notes, your home, and the web.
Fulloch is your fully private, local voice assistant running on your own PC or Mac. Ask questions, capture thoughts, and search your Obsidian vault by voice. Control your home via Home Assistant. Pull live answers from the web with SearXNG. All fully private and running on your home PC.
- Obsidian notes - read, write, append, and search your vault by voice; capture a conversation as a note without leaving what you're doing
- Semantic search - "what did I write about the car service?" finds the right note by meaning, not just keywords
- Web search - ask a question, get a spoken summary pulled live from a self-hosted search engine, optionally saved to your vault
- Conversational - holds context across a turn; follow-ups like "and tomorrow?" just work
- Memory - facts persist across restarts and build up over time
- Smart-home control - control any of your smart home devices using Home Assistant, integrate Fulloch into Home Assistant to trigger voice notifications and track your conversational history
- Music search & play - "play the Beatles", "play jazz in the kitchen", "play music everywhere" - smart search on Spotify directly and hand playback off to Home Assistant
- Calendar reminders - creates events on a dedicated HA calendar and speaks them at the right time
- Barge-in - interrupt mid-sentence with the wakeword
- Voice options - the GPU stack clones from reference audio; CPU users can choose fast built-in Kokoro voices or experimental Pocket TTS one-shot cloning
- Quiet delivery - ask it to whisper or speak quietly; every TTS backend lowers output volume to 30% by default
Higgs TTS 3: The optional Higgs GPU backend is available only under Boson AI's Research and Non-Commercial License, not Fulloch's MIT license. It requires explicit consent for every voice reference. See Model Sources and Licenses.
The default stack runs on CPU (mac/linux/windows). Audio runs through the browser dashboard. The LLM is either regex-only (simple commands) or off-box via an OpenAI-compatible endpoint you configure in the wizard (e.g. Ollama / LM Studio / another machine on your LAN). The dashboard avatar swaps to Parloch, the Partially-local home voice assistant, when the LLM is running off-device.
docker run -d \
--name fulloch-ai \
--restart unless-stopped \
-p 8765:8765 \
-e DASHBOARD_HOST=0.0.0.0 \
-v ./data:/app/data:rw \
# -v /path/to/your/ObsidianVault:/vault:rw \
ghcr.io/liampetti/fulloch:cpuSwap :cpu for :latest (the CUDA image with Qwen3-TTS voice cloning and the on-GPU 9B SLM) and add --gpus all:
docker run -d \
--name fulloch-ai \
--restart unless-stopped \
--gpus all \
-p 8765:8765 \
-e DASHBOARD_HOST=0.0.0.0 \
-v ./data:/app/data:rw \
# -v /path/to/your/ObsidianVault:/vault:rw \
ghcr.io/liampetti/fulloch:latestThe commented-out
-vline exposes your Obsidian vault to Fulloch so voice notes can read/write it. Uncomment it, edit the host path, and add a matchingobsidian.path_translationentry indata/config.yml, see the Obsidian section below.Named volumes (e.g.
-v fulloch-data:/app/data:rwin compose) work too, the image's entrypoint chowns the volume to the container's user on first boot, so no separatechowninit container is needed. The bind mount above (the default in this README) inherits the host's UID and is even simpler.
Create a shared network, then run SearXNG on it and point Fulloch at it via search.searxng_url:
docker network create fulloch
docker run -d \
--name searxng \
--network fulloch \
--restart unless-stopped \
-p 8080:8080 \
-e SEARXNG_SECRET=change-me \
searxng/searxngThen in the Fulloch setup wizard, set Web search URL to http://searxng:8080/search. (Skip the -p 8080:8080 if you don't need to reach SearXNG from the host, the Fulloch container only needs the in-network name.)
- Open
https://localhost:8765and follow the wizard. See First 2 minutes below for a step-by-step walkthrough of what to expect.
What the first two minutes of a fresh install actually look like, so nothing in the timeline surprises you:
docker runreturns in a second. The container starts and the entrypoint chowns the data dir (a no-op on bind mounts; fixes named volumes on first boot). The image is ~2 GB for the CPU stack and ~6 GB for the GPU stack; only download size, not memory.- Open
https://localhost:8765. The dashboard's first render shows the URL banner with a Copy button and a one-line note about the self-signed-cert warning. Click through the warning; it's expected for a private LAN install. - Walk the wizard. Four steps: pick a thinking mode (regex simple / full GPU / remote LLM), pick a name and voice, optionally connect Home Assistant and SearXNG, optionally connect Obsidian.
- Models download. This is the long bit. The CPU stack pulls roughly 5 GB (Qwen3 1.7B ONNX + Kokoro 82M); choosing Pocket TTS instead adds its roughly 250 MB English ONNX voice-cloning bundle. The GPU stack pulls roughly 13 GB (Qwen3 1.7B PyTorch ASR + Qwen3 1.7B PyTorch TTS + the 9B language model).
- Startup Once models are downloaded the assistant can go through startup, this will happen everytime you stop and restart Fulloch. This should only take about a minute or two with the Qwen3 TTS taking the longest to warm-up. Once everything is loaded you are presented with the chat screen. You can type text or click to activate voice mode, if you also select "Always Listen" you do not need the wakeword anymore (default is 'Hey Atticus').
The default certificate is self-signed, which is enough for browser microphone permission after accepting the warning once. To remove that warning on home network devices, create a private Fulloch CA and dashboard certificate:
python scripts/create_local_ca.py --forceThe script writes the dashboard certificate to data/certs/dashboard.crt, so it
uses the existing HTTPS configuration. It prints the one-time trust command for
Linux, macOS, Windows, iOS/iPadOS, and Android. Install only
data/certs/fulloch-home-ca.crt on client devices; never distribute
fulloch-home-ca.key. Restart Fulloch after running the script. Add extra names
or addresses before clients use them, for example:
python scripts/create_local_ca.py --force --host fulloch.home --ip 192.168.1.20The setup wizard configures Fulloch on first boot, and the settings console (gear icon, the same web UI) edits every option afterwards. Everything is reachable from the UI: wakeword, barge-in, voice, the Home Assistant connection, notes path, and web search. But if you'd rather hand-edit, the full annotated reference is data/config.example.yml.
Secrets (HA token, LLM API key, dashboard password) are stored in data/credentials.json, written by the setup wizard. Copying data/ to a new machine transfers everything. To configure headlessly, copy data/credentials.example.json to data/credentials.json and fill in the values, or set the equivalent env vars (HA_TOKEN, LLM_API_KEY, DASHBOARD_PASSWORD, OBSIDIAN_TOKEN) in .env.
The web dashboard is unauthenticated and bound to 127.0.0.1 by default. To reach it from another device, set dashboard_host: "0.0.0.0" in config and set a password in the setup wizard's finish step.
The moment you point the language model at an OpenAI-compatible endpoint, the avatar and favicon swap to a travelling version of the character (Let's call him Parloch: The Partially local home voice assistant), and the tagline reads "language model is off-device." Pick a local model again (None or the GPU 9B) and Fulloch comes home. It triggers as soon as a remote endpoint is configured, even if it is on your home network.
Note: GBNF grammar is passed through to llama.cpp-family servers (llama-server, LM Studio, Unsloth Studio), so the remote path enforces the same action/reply shape as the local one. Other OpenAI-compatible endpoints ignore the undocumented
grammarfield and fall back toresponse_format: json_objectplus a one-shot repair round-trip, so tool-call errors are more likely there.
A HACS-installable integration for status sensors, mic control, proactive speech, and automation triggers.
Or manually: HACS → Custom repositories → paste https://github.com/liampetti/fulloch, category Integration → Download → restart HA → Settings → Integrations → Add → Fulloch.
| Entity | Description |
|---|---|
sensor.fulloch_status |
idle / thinking / speaking |
sensor.fulloch_last_utterance |
Last thing the user said |
sensor.fulloch_last_response |
Last thing Fulloch said (full_text attribute has the full string) |
switch.fulloch_mic |
Mute / unmute the microphone |
text.fulloch_speak |
Submit text → Fulloch speaks it |
text.fulloch_chat |
Submit a query → full agent loop |
| Action | Field | Description |
|---|---|---|
fulloch.speak |
text |
Speak a message |
fulloch.chat |
text |
Run a full agent query and speak the result |
fulloch.mic |
enabled |
Turn the mic on or off |
| Event | When |
|---|---|
fulloch_wakeword_detected |
Voice turn starts |
fulloch_turn_ended |
Fulloch finishes speaking |
Use events in automations, e.g. dim lights on fulloch_wakeword_detected, restore on fulloch_turn_ended. Use fulloch.speak for proactive notifications from your home.
The dashboard's Entities tab blocks specific entities (locks, alarms) from voice control without affecting dashboard or automation access. Changes apply immediately.
Search-by-name music queries ("play the Beatles", "play jazz in the kitchen", "play music everywhere") need a
spotify:block inconfig.yml, Fulloch searches Spotify directly via the Web API, then hands the resolved track/playlist off to a Home Assistantmedia_playerentity to actually play. Requires a one-time manual OAuth step to get a refresh token. See thespotify:section indata/config.example.ymlfor the config keys anddata/credentials.jsonfields. Without aspotify:block, there's no music search, pause/resume/skip still work through Home Assistant regardless (so they keep working for AVR/TV too).
Connect your Obsidian vault so Fulloch reads, writes, appends, and searches your notes by voice, and knows which note you have open. The first time you connect, Fulloch offers to copy your existing notes into the vault as Inbox/fulloch-import/. Cloud sync (Remotely Save, etc.) is unchanged, Fulloch only sees the local vault.
Setup (about 2 minutes):
-
In the Fulloch setup wizard, the "Connect Obsidian" step lets you auto-detect your vault or paste its path. Click Save and continue, or Skip to do it later.
-
Open the dashboard's Obsidian tab and click Show install instructions to get a download link and your auth token.
-
In Obsidian, extract the downloaded
fulloch.zipinto<your-vault>/.obsidian/plugins/fulloch/, then enable the Fulloch plugin in Settings → Community plugins and paste the token.In the plugin settings, use the HTTPS dashboard URL (for example,
https://localhostwith port8765). A plainlocalhostvalue creates an insecurews://connection, which Fulloch redirects and WebSockets cannot follow. The default self-signed dashboard certificate must be trusted on the computer running Obsidian; use the private-CA instructions above for a permanent trust setup.
That's it. Once the plugin connects, the dashboard flips to Connected and voice notes go straight to your vault. Closing Obsidian doesn't break anything, Fulloch remembers the vault and voice keeps working.
By default, voice can only create and append notes. While the plugin is connected, the Obsidian tab has an explicit Enable edit/delete control for active-editor actions: insert at the live cursor, replace currently selected text, rename the active note, or move it to Obsidian trash. The dashboard tab pulses red while this mode is enabled; disable it again when you are finished.
To point Fulloch at a different vault later, use the Switch vault section on the Obsidian tab.
Running Fulloch in Docker? The plugin runs on the host, so it reports host paths (e.g. /Users/you/Documents/MyVault). Fulloch inside the container can't see those. Two edits to wire it up:
-
Add your vault's directory as a volume when launching Docker container
-v /Users/you/Documents/MyVault:/vault:rw -
Add the same mapping under
obsidian.path_translationindata/config.yml:obsidian: path_translation: "/Users/you/Documents/MyVault": "/vault"
Restart Fulloch. The Obsidian wizard's "Auto-detect" scans the container filesystem for a vault, so it'll find /vault (or wherever you mounted it) without further config, path translation above is still needed so the plugin's host paths resolve correctly once connected.
Common commands take a regex fast-path that skips the language model entirely, for an instant response. Compound requests ("… and …"), vague references ("turn it off"), or conversation, falls through to the full agent.
| Say | Does |
|---|---|
| "turn on/off the fan" | on / off |
| "toggle the porch light" | toggle |
| "set the kitchen lights to 60 percent" | brightness |
| "dim / brighten the lights" | dim (30%) / brighten (100%) |
| "make the lamp blue" | colour |
| "turn the volume up / down" | volume |
| "lock / unlock the front door" | lock / unlock |
| "open / close the blinds" | covers |
| "play the Beatles" | music search & play |
| "stop" · "skip" · "resume" | media control |
| "set a timer for 5 minutes" · "list my timers" | timers |
| "what time is it" | time |
| "think about …" · "summarise your thinking" | thinking mode |
Submit a Bug Report or a Feature Request.
See CONTRIBUTING.md for how to add tools and submit changes.
Model download sources and licensing notes are listed in MODELS.md.
Voices in data/voices/:
atticus/tulloch- generated with Qwen3-TTS-12Hz-1.7B-VoiceDesign (Apache-2.0) from text descriptions; synthetic, not clones of real peoplecori- sample from Piperen_GB/cori/highby Bryce Beattie, trained on LibriVox recordings (MIT / public domain)All Kokoro voices- generated with Kokoro-82M (Apache-2.0)
Pocket TTS uses a selected data/voices/<name>.wav reference for one-shot cloning; use only voices you have permission to reproduce.
MIT - see LICENSE.

