Read-only WhatsApp, on purpose
My daughter's school runs on WhatsApp parent groups — two of them, chatty ones. I didn't want to join them, and I didn't want to miss the three messages a month that actually matter. The obvious ask — “agent, read my WhatsApp” — had four possible answers, and three of them were bad. The fourth worked. Then it got replaced by something better: a tiny server my agent wrote for my Mac, so the only program holding a sensitive permission is a small one that does exactly one thing. Updated October 2026 to cover the new build.
The options, honestly assessed
| Approach | Problem |
|---|---|
| The agent platform's built-in WhatsApp connection | Only covers my chats with the agent. It can't see my personal conversations at all. Clean, but useless here. |
| Unofficial bridge libraries (the kind that automate WhatsApp Web under your number) | Against WhatsApp's terms, a real account-ban risk on my primary number, and one more always-on service to babysit. When I floated “build an MCP bridge,” this is what I was actually asking for — and the risk profile talked me out of it. |
| Forwarding messages / screenshots by hand | Free and safe, but it's me doing the monitoring with extra steps. The whole point was to not think about these groups. |
| Read the desktop app's local store on my own Mac | The desktop WhatsApp app is already signed in as me, on a Mac that's awake all day. Everything below is two different ways of reading that store — neither can send, neither can mark anything read, and nothing ever talks to WhatsApp's servers from a script. |
Round one: the platform's search
The first version used the agent platform's pairing with my Mac, which exposes a search over the desktop app's local message store — read-only by construction, on-demand, no background index. It passed its proof test immediately: one query found an audiobook link a friend had sent weeks earlier. The monitoring loop closed around it with a watermark per group and a written brief (below), and it ran every morning.
It had one wart, and it nagged at me: for that search to work, the agent app itself held Full Disk Access on my Mac — the broadest read permission macOS has, covering every file I own, granted to a general-purpose app so it could read one app's database. The job was narrow; the permission wasn't. So we rebuilt it.
Round two: a server with one job
- A tiny program, written for this. M wrote a small compiled helper in Swift. A scheduler on the Mac runs it every 30 minutes. Its complete job description: open the desktop app's message database read-only, copy out new text messages into an ordinary JSON file, write a small status file (when it last ran, whether it worked, how much it exported), and exit. It has no network code. It cannot send anything, to anyone, ever — there's no code path for it.
- It is the only thing holding Full Disk Access. The permission moved from the general-purpose agent app to this one small, single-purpose binary whose behavior can be audited in an afternoon. The agent app's own disk access was revoked — by design, it can no longer browse my Mac at all.
- The export crosses only my private network. A minimal static file server bound to the Mac's localhost serves the export folder through my encrypted tailnet, so exactly my own devices — including the agent — can fetch the status and message files. The scheduled checks (twice daily now) read from that feed, watermark against it, and triage as before.
- The gotchas were instructive. macOS ties the disk-access grant to the binary's code signature, so rebuilding the helper silently revoked its permission until it was re-added — found the hard way, now documented. Installing its scheduler as root breaks it (ownership again). And the first serving attempt — pointing the tailnet proxy straight at a folder — failed in a way that only a proper little localhost server fixed.
Then we widened it — deliberately
The narrow version exported only the two school groups. Once it had proven itself, I approved widening the same helper to export all my chats, with a one-time backfill of the full history: just under ten thousand text messages, going back to 2014. The monitoring brief didn't change — school checks still watch the same two groups — but ad-hoc questions (“didn't someone send me a link about…?”) now search a local corpus instead of hoping a live query walks past the right message.
It paid off the same day. The very first proof test, back in round one, had a loose end: my friend had sent two audiobook recommendations, and weeks of searching had only ever found one. The backfill surfaced the second in minutes — it had been sitting in the chat since early October, in a message the old search had simply never matched. Both books are now on the paperback errand list.
From export to monitoring
The scheduled check runs from a written brief — the actual artifact, scrubbed of names and IDs, updated for the new feed:
Monitor two school WhatsApp groups (a class group and a grade-wide group) on my behalf, using the message export my helper serves from my home Mac. I do not post in these groups; this is monitoring only. Never send any WhatsApp message — the tooling cannot, and neither shall you. 1. Fetch the helper's status file first. If the Mac is unreachable or the last export is stale, do NOT treat that as "no news" and do NOT move the watermarks. Note the missed check to me (at most every 3 days) and stop. 2. Read the watermark file — it stores the newest message date already seen per group. 3. For each group, take only messages newer than that group's watermark from the export. 4. Triage. Surface ONLY: school or class deadlines, dates for events affecting our class, sign-ups or contributions due, schedule changes, teacher or room-parent requests, and invitations explicitly involving my child. Ignore social chatter, thank-yous, reactions, and other families' logistics. 5. If a surfaced item has a clear future date, put it on the family calendar with both parents invited. If it's ambiguous, flag it for me instead of calendaring it. 6. Update the watermarks to the newest message actually fetched. 7. Report only if something surfaced — name the group, quote the minimum snippet needed. Otherwise: silence.
It runs twice a day. Most days the correct output is nothing at all, and “nothing” is what I get — including the standing rule that an offline Mac is reported as a missed check, never as an empty group.
The boundaries are the feature
- The broad permission now sits with the smallest program. Full Disk Access belongs to a tiny exporter with no network stack, not to a general agent app. Everything downstream of it reads a plain file.
- Read-only isn't a setting, it's the shape of the tools. Neither the helper nor the feed has a send path. There's no button to be careful about.
- Nothing leaves my own hardware and tailnet except the attention items themselves — a one-line summary and, at most, the minimum quote needed to act. Other parents' messages stay where they were written.
- It only works while the Mac is awake. A real limit, handled by confessing (missed check) instead of guessing.
Result
The two groups are watched twice daily; deadlines arrive with calendar invites attached; my number carries zero ban risk because nothing automated ever touches WhatsApp's servers. And my Mac's broadest permission is now held by the smallest, dullest program on it — which is where broad permissions belong. Companion piece to the school automations write-up, where this check is one box in the bigger diagram.
Group names, message content and identifiers are deliberately omitted — the pattern is the publishable part.
Muse