The project's GUIDE.md, word for word, at version 501c7b5: the version this site describes. The newest is on GitHub.

The Estia guide

A private AI thinking partner living at home, on your own Mac. Estia (Εστία, the hearth) runs on one Mac at home and is reached only through Signal: encrypted texts, voice notes and calls. Once set up, she reads your mail and calendars, switches Kasa lights and plugs, and plays music on Spotify. Name her what you like. Ours is called Tiffany. Estia started as Tiffany, our own thinking partner, before she was opened up for every home.

The short version, with the install in three steps, is README.md.

Contents: Meet Estia · What she can do · Reminders · Messages she starts · Languages · Language practice · A few conversations · Safe and private · What runs where · Words used here · When something is off · Slash commands · Requirements · A rented Mac (VPS) · Set it up · How long it takes · Tests and checks · Built with · What's next · Good to know · For developers · License

Meet Estia

She answers first. On a call, when you think aloud, she may ask one question back. Her built-in style is warm, curious and playful. One short, light joke may follow an answer when the moment invites it. Never when you are worried or something serious is going on, and never in place of the answer.

In Signal, her profile reads Estia (AI), or the name set as name in the config. Ours reads Tiffany (AI). She speaks one language at a time: English, or one of ten others (see Languages). Her built-in style applies until a file, soul.md, replaces it. She reads soul.md but never edits it. It sets how she sounds, never what she may do.

What she can do

"What can you do?" is answered from a list of the parts set up in this install, each in one line with one example; a part not set up is left out. The calendar line names each calendar's service (Google, Outlook, iCloud, Fastmail or Nextcloud; another CalDAV server by its host name), and the lights and plugs line names TP-Link (Kasa and Tapo), Philips Hue or both. A part with levels names them and the current one: voice answers (auto, on or off), what interrupts her on a call (only speech or any sound), the messages she starts (on or off, listed either way), Projects (off, summary, docs or code), Documents (on or off, with its folder) and the Mac's security (hardened or nuclear). She answers from it in any language, in texts; calls leave it out (spoken answers carry no lists). /help shows the same list, word for word, above the commands.

Area Example What you get
Chat "What's a good question to ask about free will?" A short, direct answer. The conversation is remembered.
Voice notes Send a voice note Transcribed on the Mac, answered in text, and optionally read back as a voice note (/voice)
Calls Call her A spoken conversation that can be interrupted. A short summary text follows a call of two or more turns.
Calendar "What's on Friday?" That day's events from each calendar, with times and places
Mail "What needs my reply?" Unanswered mail from the last week, numbered. "Read 2" opens one.
Morning digest Nothing: it comes at 09:15 on its own Today's calendar, tomorrow in one line, then the mail that needs a reply
Reminders "Remind me at 17:00 to call the dentist", /remind 20m pizza A text at that time: once, every day, every weekday or every week. A reminder is set only when a note from the code, not from the AI, says so. /reminders lists and cancels them.
Wake-up calls "Call me at 7:00 to wake me up" A text, then a Signal call at that time (once calls are set up), and one more try two minutes later if the call is not answered
Messages she starts /initiative on (off until you do) Now and then: a check-in, a calendar change or an early start, a Sunday look back, a spontaneous note. At most one a day, never between 22:00 and 08:00. /quiet pauses them, /initiative off stops them.
Language practice /babble nl A short conversation in Dutch (or one of seven other languages) at level A2 or B1, then a recap of at most three mistakes as a text
Music "Play some jazz" Spotify on your speakers (once set up)
Saved lists "What should we play next?" Ideas from a list you saved (songs, books, recipes)
Chords "What are the chords of Spooky?" Chords, key and tempo from a chord site, with a Sources message
Lights and plugs "Turn off the living room lamp" Kasa devices and Philips Hue lights on the home network, never through the TP-Link or Philips cloud
The Mac "How is the Mac doing?" A few lines on its health. Real problems are reported without being asked.
Photos Send a photo with a question An answer about the photo. The photo is deleted after the first answer.
YouTube videos Send a YouTube link, with or without a question A summary from the video's captions, with the time of each main point. Follow-up questions by text about the last video, within 10 minutes, need no new link. The captions are never stored.
Web search "Who won the match last night?" An answer that names the site and how recent it is, plus a Sources message
Places and routes "A good Italian place near me?" · "How long to the station by bike?" Up to five places with rating, price and today's hours, or the time, distance and lines, with Google Maps links (once set up)
Projects "What's next on <project>?" Open to-do items and recent changes from a project on the Mac. An extra, off by default.
Documents Send a PDF, or ask "What does my lease say about pets?" An answer from your PDFs that names the document and the page. PDFs sent on Signal are saved on the Mac. An extra, off by default.
Memory "Remember that …", /profile, /fix, /forget Saves what you tell her. Facts can be listed, corrected or removed.

Your day: calendar, mail and the digest

The morning digest comes every day at 09:15 (digest_time in the config): today's calendar first, then tomorrow in one line, then the mail that needs a reply. Declined invitations are left out. On quiet days she says so. If the Mac was off at 09:15, the digest comes when the bridge starts, if that is before noon.

One calendar or several can be read, each with a short name: Google calendars through their secret calendar link, the Outlook calendars of the Microsoft 365 mailboxes she already reads, and iCloud, Fastmail or Nextcloud calendars through CalDAV, the calendar standard they share. Outlook calendars are read with Microsoft's read-only calendar permission, Calendars.Read. With several calendars, each event carries its calendar's name ("Family: Football", "Work: 1:1 with Sam"). An invitation in two calendars is shown once. A calendar that can't be read is named with the reason, and the others are shown as usual. Events are read live and never cached. No calendar can be changed. When she looks up today, its timed events carry a mark from the code after their times: "(over)", "(now, until 15:00)" or "(in 40 min)", so "what's next?" at 14:30 gets the 15:00 meeting, not one that ended at 14:20; other days, all-day events and the digest have no marks. Examples: what's on a day, whether a time is free, when something is ("When is the dentist?").

A Google calendar's secret link is the one Google Calendar shows as "Secret address in iCal format". It works like a password, and it is kept in a file that only the calendar helper (a small program of hers) reads. A public link also works, but anyone can see a public calendar, so never make a private calendar public for her. Outlook calendars need Calendars.Read in the mailbox's sign-in. An administrator grants it once in Microsoft Entra, Microsoft's admin site, or the mailbox signs in again. Mail keeps working either way. Only the mailbox's own default calendar is read, not calendars others share with you. An iCloud, Fastmail or Nextcloud calendar is read with an app-specific password (iCloud accepts no other), kept in the same file as the secret links; its requests only read events, never create, change or delete them, and go only to that server (iCloud: Apple's icloud.com servers), encrypted.

Mail comes from the Exchange (Microsoft 365) mailboxes listed in the config. She reads mail only and never sends, moves, deletes or marks it. The headers (sender, recipients, subject, dates, and marks such as read, attachment or important) sync every five minutes. A mail's text is fetched only when needed (when you ask, or once to sort it) and is never stored. Headers, a label and a one-line summary are kept for 30 days. A mail counts as answered once you reply in the same conversation. Mail you archive in Outlook drops off her list. A mailbox whose sync fails, and whose last good read is more than 10 minutes old (two syncs, by default), gets one line in the digest, after the mail: "[work] not read since Mon 09:10: needs sign-in." The rest of the digest stays. The same line comes first when she looks up mail; at the start of a call she gets only the count and the time ("1 mailbox not read since Mon 09:10.").

Gmail is not built in. setup_assistant.md, "Gmail (not built in)" explains why and lists the options. Forwarding Gmail to a mailbox she reads works today.

Music and saved lists

Music needs a Spotify Premium account and a Spotify developer app (setup_assistant.md, "Spotify (once)").

Music is text only. Spotify's rules don't allow voice control, so calls and voice notes never start it. Examples: "play some jazz", "play my liked songs on <speaker>", "next", "pause", "louder", "what's playing?".

A speaker starts at 30% and never goes above its cap (60% by default). The bridge also tries to wake idle Bose SoundTouch speakers listed in the config, over the home network. After an answer that used outside text (a web page, a mail or the calendar), music may pause or get quieter, but never start or get louder.

A saved list (songs, books to read) goes into the profile, where it counts toward the profile's 6,000-character limit. From it she suggests what to play or read next. Chords are looked up on the web, with sources.

Lights and plugs

Kasa lights and plugs are controlled on the home network, never through the TP-Link cloud. Philips Hue lights are reached only through the Hue Bridge on the home network, never through Philips' cloud: on, off, brightness and whites, no colours. Examples: "turn off the living room lamp", "dim the bedroom light to 30%, warm white", "is the heater on?".

Each device has one rule: normal, ask first (waits for a yes, which must come within two minutes) or never off (switching it off is refused). After each change a short note says what happened, for example "Devices: living room lamp off", and says when a device doesn't answer. In English, an answer that says a light or plug was switched, dimmed or set while it was not changed gets a note from the code: "Nothing was switched." ("Nothing else was switched." when another one was). Also on a call; a plain report of a device's state ("the lamp is on") is never flagged.

Only Kasa devices are tested. Older Kasa devices need no login. Newer Kasa firmware (KLAP) and Tapo devices need the TP-Link login. Tapo devices use the same library, but they are untested. Tapo firmware with the TPAP login can't be controlled yet. The name tapo in tools/tapo/, [[tapo.devices]] and tapo.env covers Kasa devices too; tools/tapo/ and [[tapo.devices]] cover Hue lights as well.

Hue lights are untested on a real Hue Bridge so far: the tests use answers in the shape of Philips' published API. scripts/hue-pair.sh pairs the bridge: press its button when asked. The bridge is found on the home network, or its address is given. Its certificate's fingerprint, a check value, is shown and saved at pairing, then checked on every connection: nothing is sent to a bridge that shows another certificate. The key the bridge gives is never shown. Each Hue light goes in [[tapo.devices]] with brand = "hue" and the bridge's id for it (scripts/tapo-discover.sh lists them), and follows the same rules.

Photos

A photo, with or without a question: "what is this?", "what colour was it?". Up to four photos fit in one message. Follow-up questions within five minutes still see the photo. After that, the photo must be sent again. The file is deleted after the first answer.

YouTube videos

A YouTube link, with or without a question: "what's the main point?", "what does it say about sleep?". The answer comes from the video's captions only, with the time of each main point, and says when YouTube generated the captions automatically (names and terms may be misheard). Only a link in your current message is read. A video found by a web search is named, never read.

A long video is read in parts, up to its first 2 hours, after a note: "Reading a 1 h 40 min video: about 1 minute." Captions too long to read whole (more than 120,000 characters) are read up to there, and the note and her answer say how far. An answer is at most about 300 words and never holds the captions word for word: code leaves out any sentence of hers that copies 13 or more of their words in a row, or shorter stretches of 6 or more that together make up more than 30% of them, and adds the YouTube link. Asked for the full transcript, she gives the link.

Follow-up questions by text about the last video, within 10 minutes of the first read, need no new link. One video is kept at a time: a new link replaces it. The captions are kept in memory only, never on disk, and are gone after those 10 minutes or at /new. They are never stored or learned from, and /learn refuses a video link. A video without captions is not read: nothing is transcribed. Calls never read a video, since a link cannot be spoken.

Only YouTube's own site, www.youtube.com, is asked, with the video's ID (the 11-character code in the link), never the whole link. No account and no key of yours (the helper uses the public key YouTube puts in the video's page); cookies YouTube sets last only for that one read. Other YouTube addresses (a playlist, a channel, youtube-nocookie.com) are read as ordinary web pages, which hold little more than the title. YouTube's terms forbid automated access and downloading its content: a known gap, accepted for personal use (SECURITY.md).

Web search, with sources

Ollama's web search runs first, and Brave Search is the backup. Brave is capped at 2,000 searches a month by default (brave_monthly_cap; set it to your Brave plan's limit). An answer runs at most two searches. The answer names the site. For news, scores and prices, it also says how recent the source is. Links come in a separate Sources message, not in the answer. Search queries go only to Ollama and Brave. Mail, calendar, project and document text never go into a query: an answer that read them searches nothing. Results are never stored or learned from.

Places and routes

Status: built, waiting for a Google Cloud key (Places API (New) and Routes API; see setup_assistant.md, "Places and routes (once)").

Places come with rating, price and today's hours, for up to five places: "a good Italian place near me?", "is the museum open now?". Routes come with the time, distance and lines: "how long to the airport by train?". Google Maps links come in the Sources message.

"Near me" means the location last sent in Signal (attach, then Location). It is used for three hours and never logged. The message itself stays in the chat history, like any message. Without one, the home area is used. "How do I get to my 3 pm?" can use the event's place. A daily cap per API (daily_cap, 30 by default) keeps it inside Google's free allowance.

Memory and personality

She remembers the current conversation and searches older ones ("What did I say about the trip?"). /new starts a fresh conversation and keeps the profile.

"Remember that …" saves a fact at once. She also picks up facts from what you say, and reports each one saved. A fact you told her to remember, or one from /learn, is never replaced or removed by something she picked up. /profile lists the facts and where each came from. /fix corrects a fact in your own words. /forget removes one.

/learn <link> reads a page, saves only the facts about you in it, and lists what was saved.

Without soul.md, her built-in style applies: warm, curious and playful. A soul.md replaces it and sets her tone and how long her answers are. /soul shows the file in use, or why none is.

Calls

A call is answered after about two rings, with a greeting. She waits for about two seconds of quiet before she takes her turn, so a caller can think aloud. A caller can interrupt her at any time. Typing and other short noises are ignored. A listener's "okay", "yeah" or "mm-hm" alone, in the call's language, does not stop her either: she carries on, as after a cough, unless she just asked something. A quick "stop", "wait" or "but" always counts. With interrupt = "only speech" in [calls] (the default), a speech detector on the Mac (Silero VAD) decides what stops her, so road noise rarely cuts her off; until it is ready, any loud sound does. With interrupt = "any sound", any loud sound stops her. A whisper may not stop her; a radio or a passenger talking does. Her own voice from a loud speaker next to the microphone (a laptop on Signal Desktop) is told apart from the caller's and does not stop her: she carries on, as after a cough, and what the microphone heard of her own voice never reaches the model. Once she has taken in a request, she says a short "Got it." (not after a few words such as "how are you?"; acknowledge = false in [calls] turns it off). Her answer follows when it is ready. fillers = true adds a "let me think" line when it takes a while. If the caller hangs up before she answers, she stops and texts that it was not done. Silence: a check-in after 90 seconds and goodbye after ten minutes (defaults). In English, a goodbye ("bye-bye", "good-bye", "talk to you later", "take care") ends the call once her answer is spoken, also with closing words after it ("okay, goodbye, I'm going to check the log now") and also when her answer asks something; only a device question waiting for a yes keeps the call open. Leave-taking ends the call the same way, as a phrase of its own ("Okay, I have to go"), not inside a longer sentence ("I have to go to the dentist"): "I have to go" or "I'm hanging up" in English, "ik moet gaan" in Dutch, "ich muss los" in German, and a few clear phrases in the other languages ("je dois y aller", "じゃあ、切るね"). A wrap-up ("that's all") ends it too, unless she asks something. After a call with two or more turns, a short summary follows by text.

At the start of a call she knows today's numbers, never the titles: how many events are left, when the next one starts and the last one ends, and how many mails need a reply or an action. With the calendar or mail set up, she may offer the rundown once ("Three things today, the next at two. Want the rundown?"). Titles, places and senders are read only after your yes, as in a text. The numbers come from the code; she says them in the call's language.

Lights and plugs work on calls too. A device yes counts only once she has finished her question, so a yes said over the question is ignored.

Calls wait for her voice. Her voice takes up to about 15 minutes to load after an install, a move or a macOS update: the Mac's Neural Engine compiles it for her. Meanwhile a call is not answered, and a note says why: "I can't take calls yet: my voice is still loading (up to 15 minutes after an install, a move or a macOS update). I'll text you when it's ready." A second call within ten minutes gets no second note. Once her voice is ready, a code-written note follows: "My voice is ready: you can call me now." It goes at once to each allowed account whose call rang meanwhile, and to every allowed account when her voice was still loading two minutes after the bridge started. In quiet hours (22:00 to 08:00 by default, also when [initiative] quiet_hours is empty) it waits until they end, unless a call rang meanwhile. Each account gets it at most once per bridge start, except that every later "I'll text you" note is followed by one more. While /lock is on it waits for /unlock.

Reminders and wake-up calls

Your own words set a reminder: "remind me tomorrow at 7 to call mum", "in 20 minutes, the pizza", "every weekday at 8, the pills". /remind 17:00 call the dentist works even when the model is busy.

A reminder is set only when a note from the code, not from the AI, says so. The note is a separate message: "Reminder set (#12): Tue 13 Oct 07:00 · …". It shows the exact time, so a misheard time in a voice note shows at once. The first note adds "Only this note means a reminder is set.", and the welcome and /help say it too. What she writes in her own answer does not count. If none of the reminders, wake-ups or calls in her answer was set, the sentences that mention one are left out, in any of the 11 languages. The rest of her answer stays, and a line from the code says why, in English: "No reminder was set: that time has passed." Her own words about a reminder are not the proof; the note is. A note may also say "No reminder was set", but only "Reminder set" counts. /reminders lists reminders with their times. /reminders cancel N removes one.

At the set time, a short text arrives: "Reminder: call the dentist". Reminders come at night too. Quiet hours and /quiet never pause them, because you set them. Repeating reminders keep their time across summer and winter time. If the Mac was off at that time, one note after the restart lists what was missed, marked late.

With "call me", a text comes first, then a Signal call (needs [calls]): "Good morning! It's 7:00." An unanswered call is tried once more two minutes later. A declined call is not tried again. A message from you ("I'm up") cancels the second try. A call is never placed more than five minutes late. At most three calls a day. Without [calls], "call me" brings the text, and a second one two minutes later unless you wrote. Wake-up calls use an experimental feature of signal-cli, startCall (see SECURITY.md).

Only your own words make a reminder. A mail, calendar entry, web page, file, photo or shared audio file never does. A calendar event is not a reminder unless you ask for one.

For anything that matters, keep your phone's own alarm. Estia runs on one Mac. A power cut, an update, a crash or a Signal problem means no reminder and no call. A Focus or Sleep mode on the phone can silence her call unless her contact is allowed. A wake-up call is an extra.

Messages she starts herself

She writes first now and then, but only after you turn it on: /initiative on in Signal, or [initiative] enabled = true in the config. It is off until you do. /initiative off turns it off again. Four kinds, each on or off in the config:

At most one a day, never between 22:00 and 08:00, and never while she is answering, a call is on, or you wrote in the last 15 minutes. Each message ends with a line that says why it came, like "(weekly look back · /quiet to pause)". /quiet pauses all of them until /quiet off. Reminders you set still come.

The code finds every reason to write and checks every limit first. The AI writes only the words, in a separate request that never sees your mail, calendar, the web or the chat history. The code checks the text before anything is sent. /status counts replies within a day and uses of /quiet. How often she may write more is set by improvise_per_week in [initiative]. The code never raises it.

The Mac itself

Ask "How is the Mac doing?" for a short status: the model and helper services, disk, memory, uptime, recent errors, and the state of Signal, mail, calendar, lights, music, voice, calls, web search (searches today and this month) and maps (requests today).

The Mac is checked every four hours. A problem is reported once it has lasted five minutes. A "Back to normal" note follows when it is fixed. In quiet hours (22:00 to 08:00 by default) both wait until the morning, and a problem that came and went overnight is one line then. Security notes, such as a new linked device on her account or a paused model, are never held.

Projects (extra, off by default)

Setup asks whether she should see projects on this Mac, which folder, and how much. Most people: no.

Only files git tracks, never secret-like names such as .env, never outside the folder you named. The whole README is read on request at every level, a page at a time. A name heard on a call is matched to the nearest project: "Photo Book" is taken as fotobook, "garden app" as garden_app. When two names sound alike, or none is close, she asks ("Do you mean fotobook?") instead of guessing. Asked what she can see, she names each project's level and how to change it: [projects] in the config. scripts/setup.sh --extras switches projects on later.

Documents (extra, off by default)

The PDFs in ~/Estia-KB on the Mac and the PDFs sent to her on Signal: a lease, a manual, a school letter, an insurance policy. Asked later, in any words, she searches them, reads only the matching pages and names the document and the page ("lease.pdf, page 3"). Nothing found: she says so, never guesses.

Languages

Estia listens, thinks and speaks in one language at a time. Speech recognition is told the language for every voice note and call, so it never has to guess. She never switches by accident.

Language Code Whisper's error rate
English en 4.1 %
Español (Spanish) es 2.8 %
Italiano (Italian) it 3.0 %
한국어 (Korean) ko 3.1 %
Português (Portuguese) pt 4.1 %
Nederlands (Dutch) nl 4.3 %
Polski (Polish) pl 4.6 %
Deutsch (German) de 4.9 %
日本語 (Japanese) ja 4.9 %
Русский (Russian) ru 5.0 %
Français (French) fr 5.3 %

These are the languages Whisper large-v3 hears best that also have a voice. The rates are OpenAI's published per-language chart for large-v3, the better of Common Voice 15 and FLEURS (word error rate; character error rate for Japanese and Korean). Lower is better. Other languages are left out on purpose.

Language practice

/babble nl starts practice in Dutch: a short conversation at your level, and a recap of your mistakes when it ends.

A few conversations

Paraphrased; real wording varies. The music example assumes Spotify is set up.

You: play some jazz

Estia: Jazz on the kitchen speaker, at 30%.

You: tell me something fun about Mondays

Estia: (a fun fact or two first, then one light joke: a joke follows the answer when the moment invites it)

You: what's on Friday?

Estia: Two things: a team call at 10:00, and dinner at 19:00.

You: turn off the living room lamp

Estia: Done, the living room lamp is off.

A note from the bridge follows: "Devices: living room lamp off."

You: what are the chords of Spooky?

Estia: (the chords and key from a chord site, plus the tempo if it's listed)

A Sources message with the links follows.

You: how is the Mac doing?

Estia: Everything is up. The model and helpers are answering, disk and memory look fine, and there have been no new errors since the last check.

You: remember that I like short answers before noon

Estia: Noted.

A separate note confirms what was saved. /forget removes it.

You (voice note): what's on tomorrow?

Estia: (the answer as text first, then the same answer as a voice note)

Safe and private, in plain words

Known gaps are listed in SECURITY.md.

What stays home, and what goes online

The model always runs on the Mac. Outside services are reached only for Signal, links you send and the extras you switch on. Mail syncs every five minutes in the background. The digest reads the calendar each morning.

Stays on the Mac Goes online, only when used
The AI model (Gemma, run by the Ollama app) Signal, for your messages and calls (end-to-end encrypted)
Chats, memory and profile Microsoft, to read mail and Outlook calendars (read-only)
Speech-to-text and text-to-speech Google's calendar links, for Google calendars (read-only)
iCloud, Fastmail or your Nextcloud, for CalDAV calendars (read-only)
Health checks and project reading Ollama's web search and Brave Search
Lights and plugs (home network) Spotify, for music
Reading your PDFs (Apple's PDFKit and Vision), with Documents on Google Maps, for places and routes
Web pages: links you send her, and the top result of a Brave search
YouTube, for the captions of a video link you send

Setup also downloads the model (from ollama.com) and, for calls, signal-cli and the call helper's source code (from GitHub and crates.io). It runs Homebrew, the installer for Mac tools, with Homebrew's usage reports (analytics) off; brew commands you type yourself keep your own Homebrew setting.

The voice models come from Hugging Face, an online library of AI models. Whisper's files (listening) and the speech detector's are fixed to one version and checked against their fingerprints on the Mac at every start: a changed or extra file is never loaded. Whisper's are downloaded once, and Whisper then starts with no network; a Whisper folder with a changed or extra file is set aside and downloaded again at that version. The speaking voices (Kokoro for English at the first start, Supertonic-3 for the other ten languages on first use) are downloaded as published, with no fixed version and no check of their files.

What runs where

 Phone or laptop (Signal app)
        │  Signal (end-to-end encrypted)
        ▼
 signal-cli               child process of the bridge (JVM build for calls)
        │  a pipe inside the Mac (no network port)
        ▼
 Bridge: her main program (Python, no add-ons)
   ├── Database (SQLite)  memory, history, mail headers
   ├── Gemma via Ollama   the AI model on the Mac, with its tools
   ├── Voice service      speech-to-text and text-to-speech (Neural Engine)
   └── Helpers            lights and plugs, calendar, YouTube, documents, call audio
Part Where in the repo How it runs
Bridge src/local_assistant/ Always on: a background service that starts with the Mac (com.local-assistant.bridge), as her own user _estia, from a copy of the code owned by root in /usr/local/estia
Voice service tools/voice/ (Swift) Always on, as com.local-assistant.voice, as _estia, reachable only from the Mac itself
Device helper tools/tapo/ Started per command, in its own Python environment
Calendar helper tools/ical/ Started per read, in its own Python environment
YouTube helper tools/youtube/ Started per video read, in its own Python environment
Documents helper tools/documents/ (Swift) Started per PDF, as _estia; Apple's PDFKit and Vision only; needs no network
Call helper Build script, pins and patch in tools/call-tunnel/ (built on your machine) Started by signal-cli for each call
Scripts scripts/ Run, health check, install, the move into her own user, the security step, sign-ins and keys
Her commands scripts/estia, scripts/estia-deploy, installed in /usr/local/estia/bin, owned by root /usr/bin/sudo /usr/local/estia/bin/estia doctor, logs, check, sign-ins and keys, as _estia; /usr/bin/sudo /usr/local/estia/bin/estia-deploy puts an update live
Docs setup_assistant.md, docs/e2e-checklist.md Full guide and the phone checklist

Where things live on the Mac

What Where Notes
Config /var/estia/.config/local-assistant/config.toml Readable only by _estia (mode 0600), never in git. Edit it with /usr/bin/sudo /usr/local/estia/bin/estia config (nano, run as her: Ctrl-O, Enter saves; Ctrl-X quits), then /usr/bin/sudo /usr/local/estia/bin/estia restart
Personality /var/estia/.config/local-assistant/soul.md Readable only by _estia (mode 0600), written by you with /usr/bin/sudo /usr/local/estia/bin/estia config soul (nano, run as her; the first time a private copy of soul.example.md)
Service keys calendar.env, hue.env, tapo.env, web.env in /var/estia/.config/local-assistant/; mail and Spotify token files in /var/estia/Library/Application Support/local-assistant/ Readable only by _estia (mode 0600), each read only by the part that needs it. The key scripts pass a value through a pipe into /usr/bin/sudo /usr/local/estia/bin/estia secret set, never on a command line; scripts/hue-pair.sh runs as her and saves hue.env itself
Memory and history /var/estia/Library/Application Support/local-assistant/assistant.db One SQLite file; with Documents on, also the text of your PDFs
Documents (extra) ~/Estia-KB, directly in your own home Yours. Her account lists, reads and adds files there once scripts/share-documents.sh has run; .estia-share in it is the share's own small file
Her Signal account /var/estia/.local/share/signal-cli Only _estia can open it (mode 0700); her keys and her Registration Lock
Logs /var/estia/Library/Logs/local-assistant/bridge.log; her services' output (launchd.log, voice.log) in /var/log/estia, a folder only root can change Only what happened and when, never message text. Kept small by the bridge and the voice service themselves. Read them with /usr/bin/sudo /usr/local/estia/bin/estia logs
Code /usr/local/estia: releases/, current, bin/ Owned by root, readable by all, changed only by root (mode 0755): the services run only from here. Your copy of the repository (the checkout) is a source, never run. /usr/bin/sudo /usr/local/estia/bin/estia-deploy makes a new release from it and switches back on a failed check

An install from before her own user keeps the same files in your home (~/.config/local-assistant, ~/Library/Application Support/local-assistant, ~/.local/share/signal-cli, ~/Library/Logs/local-assistant) until scripts/migrate-to-own-user.sh moves them. After moving such an older install, those folders leave your home and are held for 7 days in /usr/local/estia/rollback, where only root reaches them, for /usr/bin/sudo /usr/local/estia/bin/estia rollback.

Words used here

When something is off

You see Likely cause What to do
Something is missing, or a part does not work Setup not finished, a part not set up yet, or a part failing Run /usr/bin/sudo /usr/local/estia/bin/estia doctor (read-only; scripts/doctor.sh before her own user), or send /status. Each ✗ line gives the reason and the command that fixes it, or the log to read first
"Estia is busy with another job, your answer is queued." A long job is using the model Wait. The answer follows.
"Estia isn't available right now, try again in a bit." Ollama is not reachable Check it with curl -s http://127.0.0.1:11434/api/version (or ollama ps). Start it with brew services start ollama
"Answers are paused: Ollama lists the model … as a cloud model …" Her model's name in Ollama now points to a cloud model, which runs on Ollama's servers Run /usr/bin/sudo /usr/local/estia/bin/estia doctor: its Model line gives the fix (a model downloaded to the Mac, then a restart). Commands and reminders still work meanwhile
"Voice isn't available right now; please type it." Voice service down, or still loading, or another program holds its address (doctor and /status: "another program holds its address: nothing sent"; nothing is sent to it) Check it with curl -s http://127.0.0.1:8150/health. After an install, a move or a macOS update, allow up to about 15 minutes. Another program: find it with /usr/bin/sudo /usr/sbin/lsof -nP -iTCP:8150 -sTCP:LISTEN, quit it, then /usr/bin/sudo /usr/local/estia/bin/estia stop voice and /usr/bin/sudo /usr/local/estia/bin/estia start voice.
She doesn't pick up, and a note says "I can't take calls yet: my voice is still loading" Her voice is still loading (up to about 15 minutes after an install, a move or a macOS update) Wait for her text "My voice is ready: you can call me now.", then call again. Asked "How is the Mac doing?", her status shows the voice loading its models; curl -s http://127.0.0.1:8150/health shows stt and tts as ready when it is done
"Calendar: the secret address no longer works" The calendar's address was reset in Google Save the new address with scripts/set-calendar-url.sh (with the calendar's name, if it has one)
"Calendars.Read is not granted yet" An Outlook calendar's permission was not granted An administrator grants it in Microsoft Entra, or run scripts/mail-login.sh <address> again (setup_assistant.md, "Calendar (once)")
"Mailbox <name> needs a new sign-in" Microsoft ended the sign-in (password or MFA reset, or long unused) Run scripts/mail-login.sh <address> again
"[work] not read since Mon 09:10: …" in the digest That mailbox has not been read for more than two syncs; the reason follows the colon "needs sign-in": run scripts/mail-login.sh <address> again. Any other reason: /usr/bin/sudo /usr/local/estia/bin/estia doctor shows that mailbox's state and, where there is one, the fix
"Spotify needs a new sign-in" Spotify sign-ins last about six months Run scripts/spotify-login.sh
"… isn't answering" for a device Switched off at the wall, or a new network address Check the plug. If its address changed, run scripts/tapo-discover.sh.
She says a video has no captions The video has none on YouTube Nothing to fix: a video is never transcribed on the Mac
She says every video has no captions, or that each one cannot be watched YouTube may have changed, or be blocking this Mac with a check the helper does not recognise. /status and doctor still show YouTube working then Try a video you know has captions. If it fails the same way, a newer pinned tools/youtube may be needed
She says YouTube is refusing requests from this Mac, or /status shows "YouTube: failing since …: refused by the service" YouTube blocks a Mac for a while after many requests or a bot check Wait, then send the link again. No proxy is used, so there is no way around a block
She says the YouTube helper is not set up, or doctor shows "! YouTube" The YouTube helper's own Python environment is missing Before her own user: run cd tools/youtube && uv sync --frozen. In her own user: run /usr/bin/sudo /usr/local/estia/bin/estia-deploy. If it says the root-run tools differ, read the diff, run /usr/bin/sudo /usr/local/estia/bin/estia-deploy --update-tools, then run /usr/bin/sudo /usr/local/estia/bin/estia-deploy again
/docs says "I cannot read your Estia-KB folder right now", doctor shows "✗ Documents: … not shared with her" or "no folder …", or a PDF you sent gets "Not saved: I cannot write to your Estia-KB folder" The folder is not shared with her account, or it was moved or is missing In her own user: run scripts/share-documents.sh as you, in your checkout (no admin rights; it makes ~/Estia-KB when missing), then send the PDF again. Before her own user: run mkdir -p ~/Estia-KB. If the share says ✓ and this stays, check her log for EPERM (next row)
/docs or a sent PDF says "macOS refuses" the folder to her account, doctor shows "✗ Documents: … macOS refuses it (operation not permitted)", or /usr/bin/sudo /usr/local/estia/bin/estia logs -n 5000 | grep documents shows EPERM The folder is in a folder macOS guards (Documents, Desktop or Downloads), and macOS keeps her account out, whatever the share allows. Doctor's own read runs from your SSH or Terminal session, which macOS may judge apart from her background service: her log says what she gets Use ~/Estia-KB: set [documents] folder = "/Users/<you>/Estia-KB" in her config, run scripts/share-documents.sh as you, then restart her (SECURITY.md)
Doctor shows "✗ Documents: … is not your ~/Estia-KB: the share opens only ~/Estia-KB to her" Her config still names another folder of yours (the first share's, in Documents), and the share opens only ~/Estia-KB to her Move its PDFs to ~/Estia-KB in Finder, set [documents] folder = "/Users/<you>/Estia-KB" in her config, then restart her
Doctor shows "✗ Documents: the documents helper is not built" The documents helper, the small program of hers that reads a PDF, is missing Before her own user: run cd tools/documents && swift build -c release. In her own user: run /usr/bin/sudo /usr/local/estia/bin/estia-deploy. If it says the root-run tools differ, read the diff, run /usr/bin/sudo /usr/local/estia/bin/estia-deploy --update-tools, then run /usr/bin/sudo /usr/local/estia/bin/estia-deploy again
/docs lists a PDF with "cannot be read: reading it took over 60 seconds" A scan of more than about 190 full pages Split it, and take the original out of the folder. A PDF that timed out is tried once more after each restart
Replies stop Signal's safety number changed Confirm the number in Signal first. See setup_assistant.md, Operations.
"A message came from a new linked device N and was held." A device was linked to your Signal account Yours: send /devices ok N from your phone. Not yours: remove it in Signal → Settings → Linked devices, then turn on Registration Lock for your number.
The laptop cannot reach a program on the Mac (a page you open from your laptop, a file share) The firewall is on (scripts/harden.sh --nuclear, an earlier security step, or your own choice) and does not let that program through: a Mac without a screen has nobody to click "Allow" Run /usr/bin/sudo /usr/local/estia/bin/estia doctor: a "! Firewall: … cannot be reached from other machines" line gives the two commands that let a program you started through, with its full path. After a Homebrew update the path changes: run doctor again
The move ends with a ✗ line One step failed. Her old setup came back (on a first install, nothing was installed) Keep the ✗ line: it names what failed. Fix that, run scripts/migrate-to-own-user.sh --dry-run again, then scripts/install-daemon.sh
No answer after /lock The emergency stop is on Send /unlock from the device that sent /lock, after the wait it names
"Late, I was offline …" with a list of reminders The Mac or the bridge was down when they were due Nothing: they came late, marked so. "(may be a repeat)" means one was being sent when it stopped. Keep your phone's alarm for what matters.
"Wake-up: 07:00 … No call: …" The call could not be placed: voice still loading, calls off, a call already running, or three calls that day The text says which. /reminders shows what was missed in the last week
A message you didn't ask for, ending "(… · /quiet to pause)" One of the messages she starts herself (at most one a day, never between 22:00 and 08:00) Reply if you like. /quiet pauses them, /initiative off turns them off; [initiative] in the config turns each kind off
Doctor's Always on line shows ✗ "installed, no sign of life for 12 min", "restarting, last exit code 1", "installed, stopped" or "installed, not loaded: launchd does not run it" She is installed but not working: stopped, hung, or failing at each start while the Mac starts her again Run the command doctor prints below that line: her log (/usr/bin/sudo /usr/local/estia/bin/estia logs bridge or /usr/bin/sudo /usr/local/estia/bin/estia logs launchd), her restart (/usr/bin/sudo /usr/local/estia/bin/estia restart) or the command that loads her service again. Doctor only reads; it never restarts her
No reply at all Bridge or Mac is down Log in to the Mac remotely (SSH) and run /usr/bin/sudo /usr/local/estia/bin/estia check, then /usr/bin/sudo /usr/local/estia/bin/estia logs. If that fails, the Mac itself is off or offline.

Slash commands

Command What it does
/new Start a fresh conversation. The profile stays, and old chats stay searchable.
/profile Numbered facts, with where each came from and when
/fix N new text Correct fact N in your own words. It keeps its number, and from then on it counts as yours
/forget N or /forget words Remove fact N, or the one fact containing the words (if several match, she lists them and asks for the number)
/learn <link> Learn about you from a page, and list what was saved. Not a YouTube link: captions are never kept
/status Model state, waiting messages, profile fill, last learning run, history size, Signal restarts, and each part's state: ✓ working (with the time), ○ set up but not tested yet, or ✗ failing (with the fix)
/voice auto, on or off Spoken answers to voice notes (auto, the default), to every answer, or never
/voice echo on or off Show what she heard before she answers a voice note
/voice voices, /voice name <voice> List the voices, and switch voice notes and calls to one (kept after a restart)
/soul Show the personality file in use, or why none is
/language, /language <name or code> List the 11 languages (the current one marked), or switch to one: /language nl, /language dutch
/babble, /babble <name or code> [a2|b1], /babble off Practise a language: /babble nl starts Dutch at A2, /babble nl b1 at B1; a plain /babble resumes the last one (the first time it lists them); /babble off ends it with a recap
/remind [call] [daily|weekdays] <when> <text> Set a reminder without the model. <when> is 17:00, 2026-10-13 07:00, 20m, 2h or 3d; call adds a wake-up call. Example: /remind call 07:00 wake up
/reminders, /reminders cancel N or all Your reminders with their numbers, and what was missed or failed in the last week; cancel one or all
/initiative on, /initiative off, /initiative Let her write first now and then (off until you turn it on), stop it, or see which
/quiet, /quiet off Pause the messages she starts herself, or let her write first again. Reminders you set always come
/lock An emergency stop: no answers and no calls until /unlock. Holds after a restart; needs no Mac
/unlock Ends /lock: only from the device that sent it, after a short wait
/devices The linked devices of your account that she knows, by number
/devices ok N Lets new linked device N talk to her: only from a device she already knows
/devices forget N Takes device N off the list after you unlinked it: Signal gives its number to the next device you link. A linked device silent for 14 days is held again by itself
/docs Your documents, newest first, by number, with their pages or why one cannot be read. The folder is looked at first, so a PDF put there a minute ago is listed. With Documents on
/docs send N Sends document N back on Signal, only to the account that asks; during a call it waits until the call ends
/help What she can do in this install (the list she answers "what can you do?" from), then the list above

Requirements

A rented Mac (VPS)

In principle, she runs on a rented Mac with Apple silicon and 16 GB of memory or more, such as Amazon EC2 Mac, Scaleway or MacStadium. Untested: she has never been installed on a rented Mac.

An ordinary Linux VPS cannot run her: her voice needs macOS on Apple silicon.

Set it up

Get started: a Mac with Apple silicon and 16 GB of memory or more, plus a spare phone number for her Signal account.

git clone https://github.com/enable4all/estia-on-mmini.git ~/estia && cd ~/estiascripts/setup.sh

A Mac without Apple's Command Line Tools, which hold git, asks to install them in a window on its own screen. Over a remote login (SSH), run Homebrew's installer first, which installs them (your Mac password once): /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)", then the two lines above.

Or open the folder in an AI coding assistant (Claude Code, Codex, ...) and say "set me up". It follows SETUP-WITH-YOUR-AI.md: it checks the Mac and explains each question before it comes, and you run scripts/setup.sh in your own Terminal window, where the Signal codes, passwords and keys are typed hidden. /usr/bin/sudo /usr/local/estia/bin/estia doctor checks the result.

With calls set up, calls work once her voice is ready (up to about 15 minutes after an install, a move or a macOS update); she texts you then. In quiet hours (22:00 to 08:00 by default) that text waits until they end, unless a call rang meanwhile.

With the setup script: scripts/setup.sh does steps 1 to 6 below and asks only what it must. It picks the model for the Mac's memory (16 or 24 GB: gemma4:12b; 32 GB or more: gemma4:26b), asks for her name (Enter keeps Estia), registers her Signal number with you (the captcha link, the SMS code and any PIN go into hidden prompts, as every key does), finds your Signal account from a one-time word you send her, asks which language she speaks and whether she should see projects on this Mac (most people: no), writes the config, sets a Registration Lock for her number if you say yes, moves her into her own hidden user _estia (always on), and then goes through the Mac's security step with you (scripts/harden.sh; the nuclear option is never part of setup). Run it in your own Terminal window, in the git clone at ~/estia: a ZIP download cannot be installed, because her install and every update are built from git. Over a remote login (SSH), run it inside screen (built into macOS), so a dropped connection does not stop it. Run it again: steps already done are skipped. Her name changes later with name in the config. Setup is done when /usr/bin/sudo /usr/local/estia/bin/estia doctor shows no ✗ and her welcome message has arrived on your phone: reply to it to see her answer. Or let an AI coding assistant do it with you: SETUP-WITH-YOUR-AI.md.

By hand, in short. The full guide, with every sign-in and key, is setup_assistant.md.

  1. Prepare the Mac. Install Homebrew, with its line in ~/.zprofile so new Terminal windows find it. Clone the repository to ~/estia with git, not into Desktop, Documents or Downloads (a ZIP download cannot be installed). Install the tools from Requirements. Start Ollama with brew services start ollama, then pull the model: caffeinate -i ollama pull gemma4:26b (about 18 GB; gemma4:12b on 16 or 24 GB of memory; run it again if it stops). The commands are in setup_assistant.md, 2.1 and 2.2.
  2. Give her a Signal number. The bot needs its own number, registered with signal-cli (setup_assistant.md, "Signal number (once)"). On its first start, the bridge sets the bot's Signal profile name to her name and "(AI)", so "Estia (AI)" by default. It does this again after a rename. Her profile picture is Estia's e., or your own PNG or JPEG set as avatar in the config (avatar = "" for none). The move into her own user copies that picture into her config folder when it is a file in your home of at most 5 MB.
  3. Write the config. Copy config.example.toml to ~/.config/local-assistant/config.toml, make it readable only by you (chmod 600), and fill in the bot's number and your Signal account ID. Set her name with name = "Estia", the default (ours says name = "Tiffany"; 1 to 20 letters, spaces or hyphens). Find the account ID by sending her a message and listing the senders (setup_assistant.md, 2.4). The bridge refuses a config file that other users can read. Never commit it.
  4. Personality (optional). Without soul.md, her built-in style applies: warm, curious and playful. To write your own, copy soul.example.md to ~/.config/local-assistant/soul.md, make it readable only by you (chmod 600), and edit it.
  5. Check, then run in the foreground. scripts/doctor.sh checks the Mac, the tools, the config, the model and the Signal number, and prints the fix for each ✗, or the log to read first. Then run scripts/run.sh. On her first start she sends a welcome message: what is set up, something to try, and what stays on the Mac. Reply to it from your phone. scripts/check.sh checks her health.
  6. Make it always on, in her own user. Stop the foreground bridge, then run scripts/migrate-to-own-user.sh --dry-run to see every step, then scripts/install-daemon.sh. It asks for your Mac password once. With admin rights it creates _estia (a hidden account with no password and no login), copies her config, data, Signal account and logs to /var/estia (readable only by _estia), puts a copy of the code owned by root in /usr/local/estia, installs her service file in /Library/LaunchDaemons (and the voice service's, once voice is set up) and starts her, so she starts with the Mac, without a login. She never runs as you or as root. Her release (her copy of the code) is built first, while she keeps running, so she is offline for a minute or two; over SSH, run it inside screen (built into macOS) or tmux, which keep it going if the connection drops. A first install has no rollback: the move keeps no copy, and her only copy of her data is in /var/estia. After moving an older install (one that already ran always on as you), her old folders are held for 7 days where only root reaches them: /usr/bin/sudo /usr/local/estia/bin/estia rollback goes back, with what she saved since. With voice set up, the move ends with "voice: ready". While her voice still loads, it ends with "Calls work once her voice is ready (up to about 15 minutes); she texts you then." when calls are set up (after up to 150 s of checking, so the text surely follows), and with "voice: loading (up to about 15 minutes)" when they are not. A move that succeeds also deletes the releases she does not run (from earlier tries), and lists the earlier copies of hers kept aside. /usr/bin/sudo /usr/local/estia/bin/estia remove-old deletes them together with her old folders held for the rollback: no rollback after it, so run it once she works. After a git pull, /usr/bin/sudo /usr/local/estia/bin/estia-deploy shows the new changes (commits), asks, and puts the new code live (it checks her within two minutes and switches back on a failure). Then run the security step, scripts/harden.sh (each change on your yes). Ollama starts only when you log in, so after a restart she answers once someone has logged in: "Keep it running" in setup_assistant.md, 2.8, has the settings for sleep, restarts and automatic login.
  7. Add the other parts one at a time. Each part stays off until its section is in the config: in her own user, /usr/bin/sudo /usr/local/estia/bin/estia config (Ctrl-O, Enter saves; Ctrl-X quits), then /usr/bin/sudo /usr/local/estia/bin/estia restart. /usr/bin/sudo /usr/local/estia/bin/estia doctor must show it without ✗ before you add the next (○ until its first use, then ✓). The sections and their helper scripts:
Part Config section Script or step
Voice notes and calls [voice] scripts/install-voice-daemon.sh
Calendar and digest [calendar], and [[calendar.sources]] for several calendars scripts/set-calendar-url.sh [name]; an Outlook calendar: Calendars.Read and scripts/mail-login.sh <address>; iCloud, Fastmail or Nextcloud: scripts/set-calendar-password.sh [name]
Mail [mail] with [[mail.accounts]] scripts/mail-login.sh <address>
Mail pre-filter (optional) [mail] jev_url AgentJev, run on the Mac; see setup_assistant.md, Mail
Lights and plugs [[tapo.devices]] scripts/tapo-discover.sh; newer Kasa and Tapo: scripts/set-tapo-credentials.sh; Hue: scripts/hue-pair.sh
Web search [web] in her own user, each key at a hidden prompt: /usr/bin/sudo /usr/local/estia/bin/estia secret set web.env OLLAMA_API_KEY, then BRAVE_API_KEY (Ollama's search first, Brave's the backup); on the Mac's own screen also the login keychain (security add-generic-password -U -a "$USER" -s ollama -w, -s brave-search -w), then scripts/set-web-keys.sh. Over SSH the login keychain is not the default one and stays locked
Music [spotify] scripts/spotify-login.sh
Voice calls [calls] (needs [voice]) tools/call-tunnel/
Places and routes [maps] (home, daily_cap, region) scripts/setup-google-maps.sh (quick), or the key by hand: setup_assistant.md
Health extras [health] extra_checks setup_assistant.md, Health checks
Projects [projects] Nothing to install: setup asks the folder and the level
Documents [documents] scripts/share-documents.sh (as you, no admin rights; it makes ~/Estia-KB and prints the two lines), then those two lines in her config ([documents], then folder = "/Users/<you>/Estia-KB", your folder written in full), then a restart. estia-deploy builds the helper

Keys and sign-ins go through these scripts, never through chat and never into the config file. Once she runs as _estia, they pass each value through a pipe to /usr/bin/sudo /usr/local/estia/bin/estia secret set, and sign-ins run as her (/usr/bin/sudo /usr/local/estia/bin/estia mail-login). scripts/setup.sh --extras, in your own Terminal window (it asks which ones), lists the optional parts again and shows how to add the ones you pick.

How long it takes

Downloads and builds take about 30 minutes on a 100 Mbit/s line, most of it the model download (18.7 GB); about 15 minutes on a Mac with 16 or 24 GB of memory, where the model is 8.0 GB. Voice adds about 15 minutes while her voice loads, after its build and a 1.7 GB download (about 4 minutes). Not counted: your own typing (the Signal captcha link, the SMS code, any PIN, the one-time word and setup's questions), and Homebrew with Apple's Command Line Tools on a Mac that has none.

Each step's time is measured (a fresh copy of the code on a Mac mini with macOS 27, on a line of about 67 Mbit/s, 15 Oct), computed from its download size at 100 Mbit/s, or marked not measured.

Step Time How it is known
Homebrew and Apple's Command Line Tools, on a Mac that has none Not counted Not measured
signal-cli, Ollama and uv from Homebrew, when missing: 180 MB with the parts Ollama needs About 15 seconds Computed from the size
Python 3.13 and her Python packages 7 seconds Measured
The model with 32 GB of memory or more: gemma4:26b, 18.7 GB About 25 minutes Computed from the size
The model with 16 or 24 GB: gemma4:12b, 8.0 GB About 11 minutes Computed from the size
Her Signal number, your Signal account, the questions and the security step Your own typing Not measured
Her release: her Python packages again and the documents helper's build About 25 seconds Measured
The move into her own user: the copy, her start and the health check She is offline for a minute or two; the check waits at most 2 minutes Not measured
Voice (an extra): the voice service's build About 2 minutes; 1 minute when its downloaded code is already on the Mac Measured
Voice: Whisper, Kokoro and the speech detector, 1.7 GB About 2 minutes Computed from the size
Voice: the Neural Engine's first compile of her voice About 15 minutes Measured live, 13 Oct
Voice in a language other than English: Supertonic-3, 265 MB About 20 seconds Computed from the size
Calls (an extra): the call helper's build Several GB of downloads, and a long build Not measured

Tests and checks

Built with

Piece What it does here
Gemma (Google's open model) Answers, reads tool results, decides which tool to use
Ollama Runs the model on the Mac, and is the first choice for web search
signal-cli Signal, as a command-line client
WhisperKit Speech-to-text on the Neural Engine (Whisper large-v3 turbo, fixed to one version, told the language)
Kokoro Text-to-speech in English, for voice notes and calls
Supertonic-3 (through FluidAudio) Text-to-speech in the other ten languages
python-kasa Talks to Kasa devices on the home network
youtube-transcript-api Reads a YouTube video's captions, in the YouTube helper
PDFKit and Vision (Apple) A PDF's text, and the text on scanned pages, in the documents helper on the Mac
Brave Search Web search backup
Google Places API (New) and Routes API Places and routes
Microsoft Graph Read-only access to mail
Spotify Web API Music control, by text
SQLite One database file for memory, history and mail headers
AgentJev Optional: a cheap yes/no check that marks obvious bulk mail as noise before Gemma. A separate service you run on the Mac (Apache-2.0)

What's next

Good to know

For developers

License

This project is MIT licensed (see LICENSE). The call helper in tools/call-tunnel/ is AGPL-3.0-only, because the patch it contains modifies AGPL-3.0 code; see its notice. Other third-party components and their licences are listed in THIRD_PARTY_NOTICES.md.

Not affiliated with Signal, Spotify, Google, Microsoft, Apple, Amazon, Bose, TP-Link, Philips, Fastmail, Nextcloud, Scaleway, MacStadium, Ollama, OpenAI, Anthropic, Hugging Face, Brave, GitHub, Homebrew, crates.io or OpenClaw.