The Watchtower
Overview
The attention pipeline is Melian's triage system. Every incoming email and message passes through a four-tier classifier cascade that determines urgency, routes to the appropriate handler, and decides what reaches you immediately versus what can wait.
Classifications
Each item is assigned one of five classifications:
| Classification | Meaning |
|---|---|
personal |
From a known contact. Highest priority, immediate attention. |
transactional |
OTPs, delivery notifications, receipts. Route to notify. |
spam |
Promotional or junk. Archive silently. |
fyi |
Informational but not urgent. Batch for triage. |
action |
Requires a response or decision |
The classifier also tracks its own certainty:
export type AttentionClassification = "personal" | "transactional" | "spam" | "fyi" | "action";
export type ClassificationSource = "contact" | "heuristic" | "embedding" | "llm";
export interface ClassifyInput {
body: string;
actor_id?: string | null;
chat_identifier?: string | null;
}
export interface ClassifyResult {
classification: AttentionClassification;
source: ClassificationSource;
confidence: number;
}The Four Tiers
The classifier runs as a cascade: each tier either commits to a result or passes the item down to the next.
Tier 0: Contact lookup
Before any analysis, Melian checks whether the sender is a known contact. If yes, the item is immediately classified as personal with confidence 1.0. Source: "contact". No further tiers run.
Tier 1: Heuristic patterns
Fast regex matching against the message body. No model call, no latency.
- Shortcodes, OTPs, delivery tracking keywords →
transactional, confidence 0.85–0.95 - Promotional patterns (unsubscribe links, marketing language) →
spam, confidence 0.85
Items that don't match any pattern pass through to Tier 2.
Tier 2: Embedding nearest-neighbors
The message is embedded and compared against labeled examples in Qdrant. K=20 nearest neighbors are retrieved. The tier requires at least 3 valid neighbors and a confidence floor of 0.70. A top-3 unanimous agreement check prevents noisy classifications from committing.
If the neighbors don't agree well enough, the item passes to Tier 3.
Tier 3: LLM classification
Full LLM call with the message body as context. Returns structured JSON:
{ "classification": "fyi", "confidence": 0.88, "reason": "newsletter from a known publication, no action required" }The LLM result is stored in Qdrant as a new labeled example for future Tier 2 lookups.
Mode Resolution
The classification drives how an item is handled, but the active mode determines the exact behavior:
export type EffectiveMode = "auto" | "draft" | "notify" | "triage" | "spam" | "auto_assistant";Resolution order:
spamclassification → mode"spam"(archive silently)transactional→ mode"notify"(push notification, no reply)fyi→ mode"notify"(push notification, no reply)- Falls through to the contact's configured mode preference
- If busy mode is active and mode is
"triage"andbusy_auto_replyis enabled → mode becomes"auto_assistant"(Melian drafts and sends a reply on your behalf)
State Machine
Each item moves through a defined lifecycle:
export type AttentionState =
| "received"
| "classified"
| "routed"
| "awaiting_user"
| "auto_replied"
| "spam_archived"
| "digested"
| "handled";received → classified → routed → awaiting_user → handled
→ digested → handled
→ auto_replied → handled
→ spam_archived → handledLearning & Correction
The classifier improves over time through three types of stored examples in Qdrant, all weighted differently:
| Source | Weight | When used |
|---|---|---|
| Seed | 1× | Built-in examples shipped with the system |
| LLM output | 1× | Results from Tier 3 that get stored automatically |
| Correction | 2× | Explicit user corrections override automatic results |
When you correct a misclassification, the correction is stored at double weight and becomes a stronger signal for future K-NN lookups at Tier 2. Over time, the embedding store learns your specific senders and patterns, reducing how often items fall through to the expensive Tier 3 LLM call.