---
type: note
alexandria_core: true
tags: [imessage, reference]
author: alexandria
---

# Reading iMessage (local, read-only)

The `imessage` module lets the agent read the owner's **local Messages history**
(`~/Library/Messages/chat.db`) on demand, so it can answer "what did X text me?",
"anything I haven't replied to?", or "catch me up on my thread with …". It is the
**inbound, read-only** counterpart to the outbound digest — it never sends,
replies, marks-read, or writes anything.

The reader (`Agent/scripts/imessage/imessage_read.py`) is **plumbing**. The owner
never runs it or sees a command — they ask in plain language and the agent does
the rest, then answers with a useful read, not a raw dump.

## Where it runs — locally, not on the automation host
`chat.db` lives on whatever machine is signed into Messages — the owner's
**laptop** — not an always-on server/CI runner. So, unlike the digest/sweep
engine, this is the rare capability that runs *in-session on the local machine,
wherever the owner is asking from*. Don't try to run it on a remote host; the
runner isn't signed into the owner's Messages and has no `chat.db`.

## Prerequisite — Full Disk Access (one-time)
macOS TCC blocks reading `chat.db` until the **app running the assistant** has
Full Disk Access. Grant it to Claude (or the terminal the agent runs in):
**System Settings → Privacy & Security → Full Disk Access → enable it → re-open
the app.** Until then every read returns `authorization denied`; the reader
prints that with the fix. This is a permission grant, not a stored credential.

## What the skill does when it fires
1. **Scope the ask.** Default to the last 24h across all conversations; narrow to
   a person (`--from`), a thread (`--thread`), or a window (`--since 7d`, `today`,
   a date) when the owner names one.
2. **Run the reader** (`--json` for structured input the agent can reason over).
   This is invisible to the owner.
3. **Sanity-check the coverage line before synthesizing.** Every run prints one
   line to stderr — `window <start> → N msgs / T threads · oldest <time>`. The
   `oldest` stamp must reach back to the window start; if it doesn't, the window
   wasn't covered. A `TRUNCATED` warning on the next line means a cap dropped
   older messages — re-run with `--limit 0` before answering. Both lines are
   stderr, so `--json` stdout stays parseable.
4. **Answer with a synthesis, not a transcript.** Group by person, newest first;
   **lead with anything that looks like it needs the owner** — a direct question
   to them, an unanswered inbound, a request or a deadline — then the rest as
   one-line-per-conversation *who · gist · are they waiting on you*. Link people
   to their `People/` home where one exists.
5. **Offer to drill in.** "Want the full thread with X?" pulls that conversation
   verbatim. Only show a raw transcript when the owner asks for one.

## The reader (flags, for the agent)
```sh
P="${ALEXANDRIA_PYTHON:-python3}"   # the interpreter with the alexandria-vault package
"$P" -m alexandria_vault.engine.imessage_read --vault .            # last 24h, all conversations
"$P" -m alexandria_vault.engine.imessage_read --vault . --since 7d --json
"$P" -m alexandria_vault.engine.imessage_read --vault . --from alex        # a person (name or handle)
"$P" -m alexandria_vault.engine.imessage_read --vault . --list-threads     # recent conversations
"$P" -m alexandria_vault.engine.imessage_read --vault . --thread <id|guid|name>
"$P" -m alexandria_vault.engine.imessage_read --vault . --service imessage # blue only
```
`--since` accepts `24h | 7d | 2w | today | yesterday | YYYY-MM-DD`, plus full
ISO-8601 (`2026-08-01T16:03:01`, `…+08:00`, `…Z`) for machine callers.

**The window is the bound, not a row count:** `--limit` defaults to 0 (no cap),
so a multi-day `--since` returns the whole window. Pass a positive `--limit`
only when you deliberately want the newest N — and read the stderr coverage
line either way (see step 3 above): a `TRUNCATED` warning means older messages
in the window were dropped and the read is not "caught up".

## Safety & trust
- **Read-only, full stop.** The reader opens the DB read-only and cannot send,
  reply, or write — not to Messages, not to the vault. Sending is a separate,
  deliberately-unbuilt capability; don't improvise one.
- **Trust blue, not green.** Output labels SMS with `[SMS]`; treat green-bubble
  sender IDs as forgeable for anything sensitive.
- **Contacts are PII, off-repo.** Number→name labels come from a local
  `contacts.json` (gitignored; only `contacts.example.json` ships). Without it,
  raw handles show.

## Optional config (`Agent/config.json` → `imessage`)
All optional; built-in defaults apply when absent.
- `imessage.default_since` — window when none is asked for (default `"24h"`).
- `imessage.default_service` — `all | imessage | sms` (default `"all"`).
- `imessage.contacts_file` — path to the handle→name map (default the module's `contacts.json`).
