# Annotation registry — companion annotator for imail (design sketch)

*Status: DRAFT 2026-07-16 — captured from discussion; Bob thinking it over. TODO item C155.*

## Goal

A companion app annotates each letter **before** sorting. imail's sorter (and any
other consumer — rmfmail UI, agents, future tools) reads the annotations and acts.
The mechanism should be general — not private to imail the way its `ai/` cache is.

## Decision so far: keywords on the message + a shared JSON registry

Two-layer design, no shared database required:

1. **Data plane — tags on the message itself.** IMAP keywords (core RFC 3501,
   not Dovecot-specific: any server advertising `\*` in PERMANENTFLAGS). The
   message carries its own annotations; they survive folder moves; IMAP STORE is
   atomic per message so concurrency is the server's problem, already solved.
   Provider mapping for the same concept:
   | Provider | Mechanism |
   |---|---|
   | Dovecot / Cyrus / Fastmail / iCloud / Yahoo IMAP | user keywords (atoms) |
   | Gmail | API labels (Gmail IMAP ignores custom keywords) |
   | Outlook / Graph | categories |
   | JMAP | keywords (unified with IMAP's by design) |

2. **Vocabulary plane — one shared JSON registry** (`annotations.jsonc`, draft
   next to this doc) with MCP-tool-style entries per keyword: `label`,
   `description`, `whenToApply`, `exclusiveGroup`, `setBy`, `sortAction`.
   The registry does triple duty:
   - the AI annotator's prompt is generated from `description` / `whenToApply`
     (same way MCP tool descriptions steer an agent);
   - the sorter maps keyword → action from `sortAction`;
   - UIs (rmfmail chips, Thunderbird tags) get display names from `label`.
   Adding a category = editing one file. No schema migration, no API change.

## Constraints / cautions

- **Keyword budget**: Dovecot on maildir caps keywords ~26/mailbox (filename
  letters). Registry stays small and curated; `exclusiveGroup` keeps categories
  mutually exclusive instead of accreting.
- **Atoms only**: no spaces, ASCII, prefix everything `im_` so the vocabulary
  can't collide with `$Junk`, `$Forwarded`, etc.
- **Tags, not key-value**: scores get bucketed (`im_spam_high`); anything
  non-enumerable (extracted dates, reasoning text, summaries) does NOT fit.
  If rich values are needed later, a sidecar store comes back *only for that
  residue* — the keyword layer stays as the interoperable spine. (imail's
  `.ics` writer is precedent: rich results can also just be files.)
- **Registry distribution**: share it the way accounts.jsonc is shared — GDrive
  via API with a local cache, never a file on a network path. Slots into
  rmfmail's existing config-editor/watch machinery; imail (self-contained by
  policy) keeps its own cached copy.
- **Shared SQLite over SMB is ruled out** (WAL needs shared memory; SMB locking
  corrupts). If a shared rich store is ever needed: a small annotation service
  owning WAL SQLite behind HTTP/MCP (the rmfmail-daemon pattern), or libSQL
  embedded replicas for multi-machine.

## Fit with existing TODO items

- **Q113 / Q113-AI** (per-message metadata shape): this design answers the
  cross-app half — message-attached keywords + registry. A local
  `message_meta` table remains a possible rmfmail-internal cache/residue store.
- **Q103-AI / C39 / P19** (rules engine + screener): `sortAction` in the
  registry is a candidate rule shape; imail experiments feed this directly.

## Open questions for Bob

1. Starter vocabulary — which categories actually earn one of the ~26 slots?
2. Does the annotator mirror keywords → Gmail labels / Graph categories from
   day one, or Dovecot-only first (bobma is the hot path)?
3. Where does the annotator run — IDLE-driven daemon on new mail (before
   imail's scheduled pass), or a stage inside imail's own loop?
4. Should rmfmail render `im_*` keywords as chips in the message list (it
   already syncs them in `flags_json` — display-only change)?
