Back to the Journal

On local-first, and why the cloud is a tool, not a home.

A long-form note on why every Ari runs on-device by default. What that costs us in capability, what it buys you in privacy, and how the family stays in sync over end-to-end encrypted relays — never a centralised database.

If you opened AriCore yesterday and asked it a question, the question never left your phone. The answer might have — depending on which model you picked — but the question, the previous turns, your memory, your day's notes, never did. This is not a marketing claim. It is an architectural fact, and it is what we mean by local-first.

This note explains why we built it that way, what we give up, and how the family of Ari apps stay in sync across your devices without anything we have built holding the data.

The shape.

Three rules, in order:

  • Your data lives on your device. Memories, chats, notes, intentions, threads, the day's almanac, the dream's proposals. Stored in SwiftData and a handful of JSON files inside the app's container. Nothing is uploaded.
  • If you want it on more than one device, the data goes via your iCloud — sync is opt-in, off by default. On first launch you'll see a single splash: "Pulling your notes and chats from iCloud — this only happens the first time." If you'd rather not, that's a single tap. Sync turns off.
  • When two Ari devices talk to each other directly — your phone and your Mac, say, when AriDesk pairs to AriCore — the bytes pass through a tiny relay we run. That relay is built to not be able to read them. More on this below.

What it costs.

Let's name it. Local-first costs us several things, some of which you'll notice and some of which you won't.

It costs us capability. The state-of-the-art models are big and remote. If you want the smartest possible reply, you'll pick OpenAI or Anthropic or OpenRouter in Settings, and your prompt will go to them. That's fine — the prompt is one turn; your memory, your other chats, your day's notes do not travel with it. We let you choose. We don't choose for you.

It costs us conveniences. We can't recover your data if your phone falls into a lake. (iCloud sync, if you enabled it, can. We can't.) We can't run an aggregate analytics dashboard about how the product is used, because we don't have telemetry. We can't ship A/B tests. We can't quietly study your conversations to make the model better. These are real losses for a startup. We are stubbornly comfortable with them.

It costs us, occasionally, support. When someone writes to support@arilabs.dev with a problem, we don't have their data. We have to ask, and ask carefully. This has, more than once, slowed down a fix by a week. We think the tradeoff is worth it.

What it buys.

The thing it buys you is the difference between a notebook and a feed. A notebook is yours. A feed is the platform's. The vast majority of what we type into AI products today is going into a feed. We wanted to make a notebook.

Specifically: every memory the dream proposes overnight, every line in the morning Almanac, every reflection in the evening one, every persona you've written, every skill you've installed — all of that is on your phone. If you delete the app, it's gone. If we go out of business, your data does not become someone else's asset. If a subpoena lands on our desk, we don't have anything to hand over.

The relay.

The trickiest part of doing this honestly is sync. Two devices that need to talk to each other generally need a server in the middle, because NAT and consumer networking are what they are. We run such a server. It is, deliberately, the dumbest server we know how to write.

It is a WebSocket router. It speaks five messages — hello, pair-request, pair-ack, pair-reject, and frame — and the only one that carries actual data is frame. Frame payloads are always end-to-end encrypted between your phone and your desktop. The relay sees the envelope. It cannot read the contents. It cannot forge them. It cannot store them — it has no database.

Pairing is also intentionally awkward. The desktop holds a five-minute "pairing slot" in memory; the phone joins by reading a small code from the desktop's screen; the two of them perform a key exchange the relay can't decrypt. After five minutes the slot expires. If the relay restarts, in-flight pairing attempts have to be re-initiated. There is no persistent server-side state, anywhere.

We rate-limit everything: 200 messages a second per connection, 64 kilobytes per message, 20 connections per IP. Beyond those limits, you get disconnected, not throttled. We'd rather you notice than we silently buffer your data.

$ node relay/server.mjs
[relay] listening on ws://0.0.0.0:8787
[relay] envelope-only · no logs · no db

That's the whole relay. About 300 lines of JavaScript. It is the smallest piece of code we ship.

The cloud, when we do use it.

We are not luddites. The cloud is excellent for several specific things and we use it for those things.

  • Large language model inference, when you ask for it.
  • iCloud sync, when you opt in.
  • The App Store and TestFlight, which we cannot avoid.
  • The relay, which sees envelopes and nothing else.

What we don't use it for is storing your stuff. There is no central Ari database. There is no place where, if you logged in to a website, you'd see your memories. There is no website to log in to. There's an app on your phone, an app on your Mac, and the line between them.

This is, we hope, a quietly radical thing to build in 2026. It is also a deeply unfashionable thing to build. We are choosing it on purpose, and we plan to keep choosing it. The cloud is a tool. It is not a home.

— A.

All notes Ari Labs · MMXXVI