← All projects Privacy-first · Zero-knowledge · Android · Kotlin

Monk — the focus app that couldn’t leak your data if it tried.

Focus apps have a dirty secret: they keep you watched so you’ll stay engaged. Monk inverts the deal. It silences 25 messaging apps and answers on your behalf while you’re heads-down — and it ships without the one permission every surveillance business depends on. No internet access. No account. No analytics. Not as a promise in a policy, but as a fact the operating system enforces at install time: the entire app is 1.4 MB of open-source Kotlin that processes your messages in memory and forgets them. And because the privacy is architecture rather than branding, it’s a foundation — a zero-knowledge core any developer can rebrand and build a privacy-first product on.

RoleSolo — product, design & engineering
PlatformAndroid 8.0+
StackKotlin · Jetpack Compose · Hilt · DataStore
Statusv1.0 · open source (MIT)
What this demonstrates
  • Product judgment under real constraints. A privacy claim is only worth anything if a stranger can verify it — so the network capability was removed at the operating-system level rather than promised away in a policy. The audit is one line: the manifest has no INTERNET permission.
  • Range through restraint. Auto-reply across 25 messaging apps through Android’s own quick-reply channel, a focus session that survives process death and reboots, and a whole product in 1.4 MB — no backend, no third-party SDKs, ~4,200 lines of Kotlin.
  • An honest register. The page carries its own scope ledger; when earlier drafts disclosed unwired settings, the fix went into the public repo before the claim went onto this page.
One tap. The orb turns, the countdown starts, the phone goes quiet.

The problem

The productivity category has a conflict of interest at its heart. Focus apps make money by knowing you — accounts, cloud sync, streaks, behavioral analytics. To sell you concentration, they meter your attention. Meanwhile the phone keeps swinging: studies log 60–90 notifications a day per person, knowledge workers get interrupted roughly every two minutes, and a genuine interruption costs about 25 minutes of depth. The tool that fixes this should be the one app on your phone with no angle.

People feel the conflict even when they can’t name it. 81% of Americans say they’re concerned about how companies use their data; 73% say they have little or no control over it (Pew, 2023). And the category leader in Android auto-reply — 7 million downloads — declares right in its own store listing that it may collect and share your location and app activity, and pipes conversations through cloud AI by design. The market answer to “help me be unreachable” is an app that watches you.

Monk’s answer isn’t a better privacy policy. It’s the removal of the capability. When Pew analyzed a million Play Store apps, 83% requested full network access — and that was a decade ago. Monk sits in the sliver that never asks. The promise “your messages never leave your phone” isn’t marketing copy; it’s a permission the operating system was never granted, and anyone can check.

That discipline generalizes. Everything Monk does — intercepting notifications, replying in place, running sessions that survive reboots — is a reusable pattern for any developer building software for people who cannot afford to be watched. The core is MIT-licensed precisely so it can be taken.

Who it’s for

The deep worker

Imagine you’re shipping something hard and your phone takes ninety runs at your attention a day. You don’t need another dashboard about your habits — you need the pings answered and silence until you surface. Interruptions hit knowledge workers about every two minutes, and each real one costs ~25 minutes of depth. Monk is for the overlap: people who want focus without buying it with surveillance.

The person being watched at home

Imagine the person who monitors your phone is someone you live with. In a 2025 global study, 45.7% of adults reported experiencing some form of tech-enabled abuse in the past year, and stalkerware detections run in the tens of thousands annually. For a survivor, every app is a question: what does it store, and who could make it talk? Monk’s answer is structural — it stores no messages, keeps no history, and has no server to subpoena, breach, or coerce. It isn’t an anti-stalkerware tool, and it doesn’t claim to be; what it offers is narrower and provable: an app that holds nothing and transmits nothing adds zero new attack surface to a life that can’t afford any.

The journalist and the source

Imagine your contact list is the story. At least 180 journalists in 20 countries have appeared on commercial spyware target lists. People in that world audit permissions before they install anything — and Monk is the rare app that passes the audit in one line: no INTERNET permission. “Nothing transmitted, ever” is a claim a security desk can verify in thirty seconds.

The developer who needs this foundation

Imagine you’re building for any of the people above — a therapist-referral app, a shelter’s check-in tool, a newsroom utility — and privacy isn’t your feature, it’s your precondition. Monk’s core is the head start: an OS-enforced no-network boundary, an in-memory message pipeline, and session infrastructure that survives reboots. MIT-licensed. Rebrand it, build on it, keep the guarantee.

Product journey

Three days, and one decision

Day one: the obvious build. Scaffold the whole app the way anyone would — SMS auto-reply through RECEIVE_SMS, SEND_SMS, and READ_SMS, a Room database on standby, cloud backup enabled by default. It worked. It was also three scary permission prompts and a datastore away from being every other app.

Day two: the inversion. The realization that notifications already carry a reply channel — Android’s RemoteInput — so SMS auto-reply needs zero SMS permissions; you answer a text by answering the Messages notification. The same day, the whole posture flips: SMS receiver deleted, Room deleted, backups switched off, and the commit message says it plainly — “Zero-Knowledge architecture.” Deep Focus, Do-Not-Disturb integration, boot resume, and 25-app support land in the same push: four thousand lines in a day.

Day three: the subtraction pass. The rare cleanup that removes more than it adds — net −453 lines. Release builds strip every log call; debug logs redact senders; the notification listener loses 150 lines of scaffolding. Restraint, applied to the codebase itself.

Then the positioning caught up. An MIT license, a README that leads with the conviction, and — most recently — a wiring pass that closed the gaps this page’s own earlier draft disclosed. The product didn’t change direction once; it just kept deleting things that betrayed the rule.

A single, calm loop

Using Monk takes four verbs. Onboard — three screens: the idea, the privacy promise, and the one permission it actually needs (notification access; everything else is optional and asked for in context). Begin — pick 30 minutes, an hour, two, a custom stretch, or infinity, and tap the orb. Focus — the orb turns ink-black, a countdown runs, and two live counters tally the messages silenced and replies sent on your behalf; optional Do-Not-Disturb silences the phone itself, and an optional Deep Focus mode walks you back when you wander. End — tap again: DND is restored, the counters die with the session, and nothing is remembered.

The onboarding page shown here is the whole pitch in four lines — we don’t know who you are; messages never leave your phone; no sign-up, no account, no tracking; works completely offline. Every engineering decision on this page exists to keep those four sentences true.

Monk onboarding, 'Private by Design' page: no internet access, no servers, no accounts, no tracking — with the four privacy promises listed
Onboarding — the four promises the architecture must keep.

Design principles

  • Subtraction is the feature. SMS permissions: deleted. Room database: deleted. History screen: never built — the navigation code carries the comment “Privacy: No History screen — we don’t store message history.” The best cleanup commit removed 453 more lines than it added.
  • Claims the OS enforces beat promises a policy makes. No INTERNET permission; allowBackup=false; cleartext traffic disabled. The strongest privacy statement in the repo is the absence of a line in the manifest.
  • Hold nothing you wouldn’t want to explain. Messages are processed in memory and dropped; the reply cooldown map is wiped every session; the whitelist stores contact IDs, never names; the “silenced / replies” numbers are UI, not analytics — they die with the process.
  • Ask for one permission; earn the rest. Onboarding requests exactly one thing: notification access. Do-Not-Disturb, accessibility, and contacts are optional, requested in context, and switch on the moment the user grants them — no nagging checklist screens.
  • Calm is a design system. One orb, two numbers, a monochrome ink-on-paper palette with a single brass accent. The status notification updates every 60 seconds, not every second — even the battery is treated with restraint.
  • Say what’s unfinished. This page keeps an honest-scope ledger (below), and when it lists a gap, the gap is real — and when a gap gets fixed, the fix lands in the public repo before the claim lands here.

Architecture & tech

The architecture is defined by a boundary the app cannot cross. Everything happens on-device: a NotificationListener intercepts messages and answers them through Android’s RemoteInput channel, a foreground FocusService owns the session (timer, counters, boot resume), an optional AccessibilityService handles Deep Focus and a best-effort reply fallback, and DataStore holds 13 settings keys — zero message content. There is no network tier because there is no network permission. And the honest answer to “where’s the backend?” is: there isn’t one, anywhere, and that is the product.

Notification in → in-memory pipeline → reply out through the same notification. Data never leaves the phone.
KotlinJetpack ComposeMaterial 3HiltDataStoreCoroutinesNotificationListenerServiceRemoteInputAccessibilityServiceForeground serviceProGuard/R8

Monitoring & performance

An app with no telemetry can’t page anyone when it breaks — so it has to be built not to break, and small enough to audit instead of monitor. Monitoring is inverted here: the operating system is the watchdog, the user’s own device is the dashboard, and the codebase — 4,217 lines of Kotlin across 21 files — is short enough to read in an afternoon.

1.4 MB
Release APK — smaller than one photo (the unminified debug build is 15.2 MB; R8 earns its keep)
~0.5 s
Cold start, measured on a Pixel 8 emulator (API 36, software rendering — retail hardware should do better)
0 bytes
Transmitted, ever — no network stack exists to measure

Resilience is engineered rather than observed. The session runs in a special-use foreground service with START_STICKY restart semantics; the session’s three facts (active, start time, duration) are persisted the moment it begins, so a reboot mid-session resumes with the correct remaining time — verified on the emulator: restart the phone and the countdown picks up where the wall clock says it should, and a session that expired while the phone was off is detected and cleared rather than resurrected. A battery-optimization check deep-links to whitelist the app on aggressive OEMs.

The crash surface is thin by construction: no network stack, no database, no serialization layer, no third-party SDKs beyond AndroidX and Hilt — the categories that generate most production crashes simply aren’t present. Memory sits at ~21–37 MB PSS on the same emulator, and the status notification refreshes every 60 seconds rather than every second. The only statistics the product keeps — messages silenced, replies sent — are computed on-device, shown to the user, and kept by no one.

Honest scope — true at the current HEAD
  • The fallback reply path is best-effort. When an app exposes no quick-reply action, Monk asks the accessibility service to type the reply — but that only works once the target app’s window is open. RemoteInput is the primary path and carries the product.
  • Whitelist matching is name-based. Whitelisted contacts are matched by the sender name on the notification; group-chat senders and apps that don’t title notifications with the contact name won’t match.
  • Not on the Play Store. v1.0 is built from source and sideloaded — distribution is the repo itself.
  • Performance numbers are emulator-measured (Pixel 8 AVD, API 36, software GPU), not retail-hardware benchmarks.
  • Recently closed: per-app toggles and the contact whitelist existed as UI without enforcement in v1.0’s first cut; a July 2026 wiring pass connected both to the listener and made reply text, cooldown, enabled apps, and the whitelist survive process death and reboots.

Challenging features

1 · Proving a negative

Every app says “we don’t collect your data.” Nobody can check. The bar here was different: make the claim verifiable by a stranger in under a minute. The first move is the boundary — don’t promise no exfiltration, remove the capability. With no INTERNET permission in the manifest, the kernel refuses the socket. 83% of Android apps ask for the network; Monk can’t use it without shipping a new APK that any user could diff.

The second move is closing the side doors, one by one. Cloud backup is off (allowBackup=false — Android’s backup service would otherwise happily ship app data to Google’s servers on its own). Release builds strip every log statement via ProGuard (logcat is readable over adb; a leaked sender name in a log line is still a leak), and even debug builds redact senders to M*** or ***1234. There is no database and there are no files — device forensics find 13 settings keys, not conversations. The third move is source: MIT-licensed, ~4,200 lines, rebuildable — the audit is git clone plus aapt dump permissions. The result is a privacy claim built to survive a hostile security review: demonstrated, not asserted.

ON DEVICE · BOUNDARY ENFORCED BY THE OS AT INSTALL TIME Notification from 25 apps Extract in memory only Reply RemoteInput Forget nothing kept EVERY EXIT, LOCKED Network INTERNET never declared Backups allowBackup = false Logs stripped in release redacted in debug Disk 13 settings keys zero messages The audit: git clone → aapt dump permissions → 60 seconds Cloud / servers none exist
The airlock: one pipeline inside an OS-enforced boundary, with every exit individually locked.

2 · Replying without reading

The naive way to auto-reply is spyware: scrape each chat app’s screen with an accessibility service and type into it. The elegant way hides in plain sight. Any notification with a quick-reply action carries a RemoteInput — a system-blessed channel to answer through the notification itself, uniform across WhatsApp, Signal, Slack, Telegram, and 21 more. No per-app SDK, no chat database, no message ever stored.

The quirks are where the work lived. Many apps tuck their reply action into the WearableExtender rather than the standard action row, so Monk probes wearable actions first. Every RemoteInput key must be filled or the app rejects the intent. Group chats mangle sender identity, so extraction falls back through summary text, “N messages” titles, and colon-prefixed lines. And the answered notification must be cancelled, or you surface from a two-hour session to a wall of already-handled pings.

The demo here runs with the network hardware switched off in spirit and in fact: an SMS arrives, Monk answers it through the Messages notification, the counters tick — and the punchline is that SMS auto-reply uses zero SMS permissions. Day one implemented RECEIVE_SMS / SEND_SMS / READ_SMS the invasive way; day two deleted all three, because the Messages notification already carries the reply channel. Three scary permission prompts became zero.

Two texts arrive mid-session; Monk answers both. No SMS permission involved.
THE NOTIFICATION, DISSECTED Maya · WhatsApp “are we still on for tonight?” Mark as read Reply ⌨ RemoteInput MONK’S PROBE ORDER 1 · WearableExtender actions checked first — many apps hide reply here 2 · Standard action row first action carrying a RemoteInput wins 3 · Fill every RemoteInput key partial fills get rejected by the app 4 · actionIntent.send() the reply travels through the app itself 5 · cancelNotification() no wall of answered pings at session end Day-one SMS path RECEIVE_SMS · SEND_SMS · READ_SMS + 84-line SmsReceiver — all deleted: the Messages notification already replies
One uniform reply channel across 25 apps — and the deleted invasive path it replaced.

3 · The echo problem

Failure modes nobody thinks about until 3 a.m.: your own reply posts a notification, and the listener answers itself, forever. Two people running auto-repliers ping-pong until a battery dies. A chatty group thread collects fifteen “I’m in monk mode” messages in a minute — congratulations, you’re a spam bot now. The defenses are small and layered: a self-reply guard that recognizes and skips Monk’s own words when they echo back; a whitelist gate that lets chosen contacts through untouched; and a per-sender cooldown — 5 minutes by default, tunable from 1 to 60 — so each person gets one courteous answer, not a barrage.

The detail worth a second look is where the cooldown ledger lives: in a plain in-memory map, created when the session begins and destroyed when it ends. This is a rate limiter designed under a constraint most infrastructure never faces — the ledger itself must not exist after the session. Rate limiting that leaves no trace of who messaged you.

Message posted focus session active Own reply? matches Monk’s words skip — the echo guard Whitelisted? chosen contacts let through untouched Cooldown live? 5 min default · 1–60 silence only — no second reply Reply · record · cancel THE COOLDOWN LEDGER in-memory map created at BEGIN sender → last-reply time destroyed at END never written to disk THE AVOIDED FAILURE two auto-repliers, answering each other
Three gates before a reply — and the rate-limit ledger that must not outlive the session.

4 · Deep Focus, without becoming the thing it fights

The uncomfortable fact: “bring the user back to the app” uses the same API family as stalkerware. Android’s AccessibilityService can read every screen, watch every field you type into, and harvest every chat. The design question isn’t can you — it’s how little can you take.

Monk’s answer is verifiable in code. The service listens only for window-state changes — which app came to the foreground, never what is on it. When you wander mid-session, it walks you back with a 500 ms cooldown, a firm hand rather than a seizure-inducing loop. The permission is strictly optional and off by default; deny it and everything else works. The system settings dialog explains in plain words what it does, and the service’s entire configuration is a nine-line XML file anyone can read.

The contrast is the point: the same service could read every screen. It configures itself not to — and the loop here shows the whole feature: press home, land on the launcher for half a second, and Monk is back in front of you.

Deep Focus — leave mid-session, and Monk walks you back.
WHAT THE ACCESSIBILITY API ALLOWS read the content of every screen in every app watch text fields as you type including passwords harvest chats, mail, banking — the stalkerware toolkit the API surface abuse is built on WHAT MONK CONFIGURES window-state changes only which app is foreground — never content relaunch, 500 ms cooldown a firm hand, not a strobe optional · off by default deny it and everything else still works the whole config: nine lines of XML SAME API · OPPOSITE INTENT — AND THE DIFFERENCE IS READABLE
The capability surface versus the configured surface — restraint you can diff.

5 · A session that refuses to die

A focus session is a promise with a deadline, and Android is an environment that kills processes for sport. The session has to survive the OS’s memory pressure, the user’s reboot, and Doze’s clock games — without holding a wake lock like a barbarian. The machinery: a special-use foreground service (Android 14’s FOREGROUND_SERVICE_SPECIAL_USE with a declared focus_mode subtype — the modern, policy-compliant way to stay alive) with START_STICKY restart semantics; the session’s three facts persisted the moment it starts; and a boot receiver that recomputes remaining time from the wall clock. A 2-hour session with a reboot in the middle ends exactly two hours after it began — and a session that expired while the phone was off is detected and cleared, never resurrected as a zombie. Frugality is part of the reliability story: the timer is a single coroutine delay, the notification refreshes on a 60-second cadence, and persistence costs three DataStore keys, not a battery.

ONE TIMED SESSION, WALL-CLOCK TRUE BEGIN END exactly on time 3 facts persisted process killed memory pressure → START_STICKY restart phone reboots BootReceiver resumes; remaining = wall clock off past the end expired while powered down → cleared, not resurrected persisted to DataStore: focus_active · focus_start_time · focus_duration_ms the only three facts a session leaves behind — and they’re deleted at END
Failure injections against one timeline: killed, rebooted, expired — the session answers all three.

6 · The discipline of one screen

Every element on a focus app’s screen is a bid for attention — which makes screen design a zero-sum game against the product’s purpose. Monk’s home screen holds exactly four things: a word, an orb, a duration, and — in session — two numbers.

The craft is in what the orb carries. Its entire state language is color and a two-second breathing pulse: paper for ready, ink for focused, gray for locked. Commitment gets one 50-millisecond haptic tap. Duration is five pills, not a picker. The counters render in hairline-light type, and the palette is near-black ink on warm paper with a single brass accent that appears almost nowhere — restraint as a visual identity.

Just as deliberate is what’s absent: no tab bar, no streaks, no charts, no red badges, no history screen. The absence of gamification is a feature with a straight face — this app rewards you by not being interesting. Even the status notification plays along, updating once a minute instead of once a second.

Monk mid-session: the ink-black END orb, a 28:51 countdown, and live counters showing 2 messages silenced and 2 replies sent
Mid-session — one orb, one countdown, two honest numbers.

Selected screens