feat(email): echo input message_id in triage result#1551
Conversation
EmailTriageResult had no way to identify which input produced it: a consuming app polling the triage endpoint could not correlate a result back to its email or deduplicate across polling loops. The response now echoes the input's identifying id — message.message_id for a single email, thread_id for a thread — via a new optional message_id field. The field is Optional (default None), preserving the frozen #1262 contract shape: existing callers that omit it keep validating unchanged. The echoed id IS the dedup key — a consumer caches results by it; no server-side result cache or GET-by-id endpoint is needed (keeps the contract stateless).
SummaryClean, well-scoped additive change: The single most important thing: there's nothing blocking here. Test coverage is genuinely good — both engines, both single/thread, and explicit backward-compat assertions — and the tests exercise the real Issues Found🟢 Minor — history-tagged class docstring (contract.py:235) 🟢 Minor — comment could be one line (email_routes.py:248) 🟢 Minor — backward-compat tests overlap test_contract_schema.py (test_msgid_echo.py:396) Strengths
VerdictApprove with suggestions — only 🟢 minor doc/comment nits; the behavior, tests, and backward-compat guarantees are solid and safe to merge. (Note the PR is stacked on #1547; merge order applies.) |
Closes #1539
Why this matters
Before: the triage API accepted
message_idon input but never echoed it in the response, so a consuming app couldn't correlate a result back to its input or dedupe — it would re-triage the same emails on every polling loop.After:
EmailTriageResultechoes the input's identifying id —message.message_idfor a single email,thread_idfor a thread — across both engines and both single/thread paths. The consumer caches by this id to dedupe. The field isOptional, so the frozen #1262 shape stays backward-compatible (existing callers that omit it still validate). Per the AC, consumer correlation is the documented echoed-id route; no stateful server-side cache is added.Test plan
message_idfor single (heuristic + llm) and thread; validates without the field (backward-compat); sample payloads parse. 374 passed.Real-world evidence (live
gaia api start, engine=heuristic; branch + field verified before testing)message_id=rw-single-1)thread_id=rw-thread-7)rw-single-1rw-thread-7rw-single-1rw-thread-7Both OSes: single echoes the input
message.message_id, thread echoesthread_id. Boxes restored to their original branches after the run.