Skip to content

ADR 0022: Isolated, schema-validated AI inference

Status

Accepted

Context

Sprint 05 Stream B must classify mail, extract facts, and summarize threads without mutating providers. Governing spec 01 AI §§6–9 and §§12–14 requires typed versioned output, immutable history, abstention, and isolation from sync. Stream A shipped evaluatePolicy as ADR 0019. MAIL-36 shipped the golden-set harness as ADR 0020. MAIL-170 shipped suggested drafts as ADR 0021. ENABLE_SEND stays off.

Decision

  1. Persist every model call as an immutable AIInference with provenance. Reprocessing inserts a new row. Current designation lives in current_ai_inferences, not by updating history.
  2. Validate structured output against versioned JSON schemas before writing business Classification, ExtractedEntity, or ActionItem rows. Schema failure retries, then records error/abstention.
  3. Treat classification, extraction, and summaries as recommendations. Display thresholds are configuration. Automation from this worker is always false and stricter than display.
  4. Call Stream A's evaluatePolicy (actor: AI) for send / delete / archive. If the gateway is missing, deny side effects locally. The AI layer still refuses those actions even if a future gateway would allow them. Deterministic skip_ai matches from MAIL-86 may skip the model and never execute rule command requests.
  5. Isolate AI from sync: enqueue and inference failures leave MailMessage / MessageInstance upserted. Timeouts and prompt-injection content produce no adapter writes.

Alternatives

Consequences

Application modules live in @inbox-agent/application (ai-inference.ts, classify.ts, extract.ts, summarize.ts, ai-isolation.ts). Migration 0015_ai_inference_current_pointer.sql adds the current pointer, taxonomy/error columns, and an immutability trigger. Golden-set and draft work stay on other issues. ADR 0019 remains the policy gateway. ADR 0020 remains the golden-set harness. ADR 0021 remains suggested drafts.