Privacy by default: keeping your daily tasks off vendor clouds
When you grant an automation tool the Android Accessibility permission, you give it the ability to read the UI of every app on your phone. That permission is what makes Ukkin possible. It is also why this isn’t a category where the polite cloud model — “send a sanitized hash to our API for analytics” — is acceptable. The threat model is too lopsided. The user is risking too much for us to risk it cheaply on their behalf.
This post is about how that constraint shapes what gets built. We’ll cover what we mean by “local-first by default,” what the realistic threat model looks like, what’s actually local in Ukkin today, and where we’re being honest about gaps that are still open.
The threat model in one paragraph
A phone agent, by construction, has line-of-sight to anything that appears on your screen. It does not get to filter what it sees; it sees the whole UI tree of whichever app is in focus. Even if the agent’s job is just to read a single price field on a wishlist page, the operating system surfaced the whole page to it. If anything in that page leaks — to a log, to a remote LLM call, to an analytics ping — the user’s exposure isn’t “the field the agent acted on.” It’s “everything the screen contained at the moment the agent ran.”
The exposure compounds with frequency. A price-watch agent that runs daily for a year sees a year’s worth of whatever was on adjacent screens. A morning-email-summary agent sees a year’s worth of inbox. A “watch my Instagram mentions” agent sees who you follow, what you scrolled past, who slid into your DMs. None of those are things the agent is supposed to care about. All of them are things it has technical line-of-sight to.
A cloud-by-default architecture in that environment is not “convenience with a privacy footnote.” It’s a continuous data leak with the user paying the bill.
What “local-first by default” actually means
The phrase gets thrown around. Here’s what we mean by it operationally.
The runtime has no required cloud dependency. Once an agent is built, it should be able to do its job — read a screen, find an element, tap a button, save a tracking entry — without making any network call to a service we control. That’s the hard line. An agent that can only run when our backend is up is, definitionally, not local-first.
Data the agent observes stays in storage you control. Tracking history lives in a local SQLite. Past screens, if cached at all, sit in a sandboxed app directory the user can wipe by uninstalling the app. The agent does not phone home with “here’s what we saw today.”
The understanding layer runs on-device where it can. The phone’s vision-language model, the speech path, and (Phase 2) the language model that interprets the agent-authoring conversation are designed to run locally. The Phase 2 commitment to “on-device LLM for natural language understanding” is exactly this — we don’t yet ship a fully on-device LLM in every build, but the design target is that we should, and the architecture doesn’t bake in a cloud assumption.
The user is the auditor. Open-source matters here. Anyone can check what the agent is doing with their data, what it caches, what it sends. The MIT license isn’t a virtue signal; it’s the only credible answer to “why should I trust you?”
What this rules out, even if it would be convenient
Some things become harder when you commit to this posture. We mention them out loud:
- No telemetry from the runtime. We don’t get to ping a server when an agent runs. We can’t tell how often
PriceWatcherAgentfires. We can’t see your error logs unless you choose to send them. - No cross-device sync without explicit opt-in. Your agents and tracking history live on the device they were created on. Moving them to a new phone is a manual export/import flow, not a magic iCloud-style restore.
- No server-side LLM for screen understanding. The screen-understanding model has to be a local one. That means it’s smaller and dumber than a cloud frontier model. We accept the tradeoff.
- No analytics dashboard for us. We are flying without instrumentation when it comes to how the product is used. We rely on people opening issues, not on Mixpanel funnels.
Some of these are genuinely costly. Engineering is harder without telemetry. Product decisions are harder without analytics. We’ve decided the alternative is worse.
Where Ukkin is honest about the gap
We are not going to claim everything is perfectly local today. The current state of things, as honestly as we can write it:
- The agent runtime itself is local. Once an agent is created and the Accessibility permission is granted, the runtime does its work without contacting us.
- The agent-authoring conversation, in some builds, still uses a cloud LLM. The Phase 2 roadmap line about an on-device LLM is exactly because we want to close this gap. Until then, what you type into the agent-builder chat may be processed by a hosted model in some configurations. The contents of your screens never go there — only the natural-language description you typed. But it is, technically, a network call. We’d rather you know.
- Some app integrations may use vendor APIs. Where an app has a public API that’s nicer to integrate with than Accessibility (e.g. a calendar service), an integration may call that API. That’s a network call to the app’s vendor, not to us, and it follows whatever the app’s own privacy terms are.
- Crash reporting is opt-in. If you turn it on, we get a stack trace, not your data. If you leave it off, you get true silence from us.
The reason we list these is that the only credible version of “privacy by default” is one where the gaps are named. A privacy promise that only holds when nobody looks isn’t a promise; it’s a slogan.
Why this matters more for agents than for chat
A chat assistant has a clean story: don’t put sensitive things in the chat, and the model never sees them. The user is in control of the input.
An agent isn’t asking for input. It’s looking at your screen on its own schedule. The user does not get to redact what the agent sees. That’s why the privacy story for agents has to be enforced by architecture, not by user discipline. Telling someone “be careful what’s on your screen when the agent runs” is a non-answer. The agent runs in the background; the user has no idea what was on the screen at the time.
The only architecture that survives that constraint is one where what the agent sees doesn’t leave the device unless the user explicitly asked it to.
What you can do today
If you install Ukkin, the practical steps that map to this posture are:
- Grant the Accessibility permission knowing what it gives the app access to — everything on screen for the apps you allow.
- Leave crash reporting off if you want true silence from the agent’s runtime.
- Look at the source for the specific agents you care about. They’re small and readable.
- Keep the cloud-based agent-authoring step short. Describe the workflow, accept the generated agent, and let the runtime do its thing offline.
We will keep closing the gaps. The on-device LLM milestone is the biggest open one and it’s where most of the next batch of work goes. When it lands, the agent-authoring step won’t need a network call either, and the local-first claim will be local-first all the way through.
Until then, we’d rather walk you through where the line currently is than dress it up. That’s what privacy by default means — not a marketing badge, but a discipline of being specific about what’s local, what isn’t, and what’s coming.