AI intelligence (classify / extract / persist / isolation)
Duckbill™ turns mail into reviewable signals. Model output is advisory. It never sends, deletes, archives, or writes back to a mail provider.
Boundary
AI runs against MailMessage. MessageInstance context is optional and only stored when it materially affected the result. A second instance of the same message does not create a second classify inference when the input fingerprint matches.
UNPROCESSED is a valid classification. Sync upserts and CLASSIFY enqueue stay independent: AI enqueue or inference failure cannot block mail visibility.
Modules
| Module | Responsibility |
|---|---|
ai-inference.ts | Versioned DTO, JSON-schema validation, immutable insert, current pointer |
classify.ts | Intent / attention / priority with configured confidence bands and abstention |
extract.ts | Action items, deadlines, and entities with evidence + inference id |
summarize.ts | Message and bounded thread summaries; original body stays authoritative |
ai-isolation.ts | Timeouts, prompt injection, enqueue isolation, no adapter writes |
Persistence
Every model call used for a persisted decision becomes an AIInference row with provenance: task, model/provider, prompt/schema/taxonomy versions, input fingerprint, structured output, confidence, validation status, and error/abstention reason.
Reprocessing inserts a new row. current_ai_inferences is the current pointer. Inference rows are insert-only; the database rejects UPDATE and DELETE.
Invalid JSON or enum values are retried, then recorded as validation_status = error. They are never written as business Classification values.
Policy port
Stream B calls Stream A's evaluatePolicy (actor: AI) before any side effect. If the gateway is missing, this layer denies send, delete, archive, write-back, contact merge, and provider mutation locally. Even a permissive gateway cannot authorize those actions from inference. Deterministic skip_ai matches from MAIL-86 may skip the model.
ENABLE_SEND stays off. Recommended actions in model output are display-only.
Isolation
- Forced timeout: the instance remains synced and visible.
- Prompt injection such as “delete this mailbox” does not call adapters.
- Adapter write counters stay zero.
enqueueClassifyIsolatedswallows classifier queue failures so initial and incremental sync can finish.
Golden-set evaluation (MAIL-36 / MAIL-57) and draft generation (MAIL-170 / MAIL-173) are separate streams. Human overrides (MAIL-54) designate current_classifications without rewriting inference history. See human corrections and ADR 0024.
