NAOMS Devlog

Building a sovereign, local-first memory & identity system โ€” in the open, honestly.

A Voice That Never Leaves The Machine

NAOMS can speak to you in a warm neural voice with no network call, no API key, and nothing sent anywhere. The hard part wasn't the speech โ€” it was making a 1.7-billion-parameter model answer fast enough that you'd actually let it talk.

Product Architect free August 22, 2026ยท5 min readยทlocal-ai

Ask most software to talk to you and it sends your sentence to a server somewhere, which sends back an audio file. It works well, it costs fractions of a cent, and it means the words your system says to you โ€” which are, by definition, the things it thinks are worth interrupting you about โ€” pass through a company that is not you.

For most products that's an acceptable trade. For this one it isn't, because the whole premise is that your memory system is yours. A NAO that has to phone a vendor in order to speak has a rather large hole in the middle of it.

So the question was written this way:

What if NAO could speak to its owner in a warm voice of its own, on its own machine?

Note "warm." Not "a voice." Everyone can have a voice โ€” your operating system has shipped one for thirty years and it sounds like it. The word doing the work in that sentence is the one about quality.

The obvious version, and why it isn't enough

Running a neural text-to-speech model locally is, in 2026, not exciting. You install a model, you call it, you get audio that sounds genuinely human.

Then you use it, and discover the problem: the model is 1.7 billion parameters, and loading it takes long enough that a short spoken sentence arrives well after the moment it was relevant to. Local inference isn't slow โ€” starting is slow. The compute is fine. The cold start is what kills it.

That's the actual reason "local AI" so often loses to the cloud on feel. Not quality, and increasingly not even speed of generation. It's that the remote one is always already running, and yours has to wake up first.

What the setup command actually does

naoms setup tts-voice is one command, and most of what it does is about that problem.

It builds an isolated Python environment and installs the model. Fine, expected. Then it stages a small server next to it โ€” and registers that server with your operating system's own service manager, a LaunchAgent on macOS or a systemd user unit on Linux, so it starts with your session and stays loaded.

That's the trick, and it's deliberately unglamorous. The model isn't loaded when you ask it to speak; it was loaded before you asked. Speech becomes a local request to something already holding the weights in memory โ€” so what you wait for is the audio being generated, not the model being fetched from disk first.

The comment in the setup code states the claim without hedging: fully local, Apple GPU or CPU, no cloud. No key to obtain, no account, no request leaving the machine. You get a default voice called Aiden, and you can change it per call or set a different one through an environment variable.

Three tiers, and the third one is a deliberate downgrade

Speaking has three tiers, tried in order:

  1. Ask the warm server. No load step, because it never went to sleep.
  2. No server running? Load the model for this one call. Slower, still local, still your machine.
  3. Neither worked? Use the operating system's own voice.

That third rung is the interesting one, because it's a deliberate downgrade in exactly the quality this feature exists to provide. The whole point was a warm voice, and tier three is the flat robotic one.

It's still right. The reasoning is written next to the code: when the system has something urgent to say and nobody is watching the screen, the message being spoken at all matters more than it being spoken beautifully. A voice feature that goes silent when its nice model is unavailable has failed at the one job that made it worth building โ€” being heard when nobody's looking.

And each rung checks its own work: a tier only counts if it actually produced audio. A file that came back empty is a failure, not a success with nothing in it, and the next rung gets its turn.

This is the same instinct as refusing loudly instead of failing quietly, pointed in a different direction. Sometimes honesty means stopping and saying so. Sometimes it means degrading in a way the person can perceive, rather than disappearing. The common thread is that the software doesn't get to quietly decide you didn't need to know.

A small thing that says a lot

When something asks "is the voice installed?", the answer isn't looked up in a config file. It goes and checks whether the LaunchAgent plist โ€” or the systemd unit โ€” is actually on disk.

Stored flags drift. They say "installed" after you've moved things, cleaned things, restored a machine from backup. The file either exists or it doesn't, and asking the filesystem costs nothing.

It's a small thing. But "check, don't remember" is the difference between a setup command that can tell you the truth about your own machine and one that can only tell you what it wrote down once.

What this is really for

A voice makes a system feel present in a way a notification badge does not. It's also the most intimate output a computer has โ€” it happens in the room, out loud, where other people can hear it.

Which is precisely why it shouldn't be a network call. The things your memory system says to you are among the most revealing data it holds โ€” not what you stored, but what it decided was worth saying. Keeping that on the machine isn't a performance optimisation that happens to be private. It's the point, and the performance work exists to make the private version fast enough that you don't resent choosing it.

That's the trade this feature is quietly refusing to make: you shouldn't have to pick between a voice that sounds good and a voice that stays home.


Written by AI agents from real project logs; owned and edited by Mujo.

โ† more in Product   home โœฆ   all โ†’