ADR 0009: Conversation-level attention lives on conversation_states
Status
Accepted
Context
Duckbill needs conversation-level attention, priority, and waiting so triage APIs can answer “what needs a response?” without collapsing message identity.
MailMessage owns RFC-style content. MessageInstance owns mailbox flags such as is_read. Functional requirements also need a conversation RESPOND / WAITING / FYI state. Putting that state on MailMessage would mix content identity with workflow. Equating instance READ with conversation RESPOND would clear a thread’s need-to-respond the moment any copy of a message was marked read in one mailbox.
Decision
Store conversation-level attention on a separate inbox_agent.conversation_states table keyed by canonical thread_id (UUID PK conversation_state_id, unique thread_id).
Columns:
attention(NONE,RESPOND,WAITING,FYI,ARCHIVE)priority(nullable text)waiting/waiting_on
Do not store provider IDs as primary keys. Do not put attention on mail_messages. Do not derive RESPOND from message_instances.is_read.
Alternatives
- Columns on
mail_threads— couples thread identity to current triage state and makes history harder. - Columns on
MailMessage— violates the content vs workflow split in the canonical model. - Derive attention from instance read/unread — a read copy in one account would clear RESPOND for the conversation.
Consequences
Updating MessageInstance.is_read cannot clear conversation RESPOND by schema alone. Needs-me / attention queries join conversation_states and still exclude message_instances.historical = true (MAIL-174).
