Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(email): route multi-mailbox message actions by recorded provenance (#1707)#1735

Merged
itomek merged 2 commits into
mainfrom
tmi/issue-1707-multimailbox-provenance
Jun 18, 2026
Merged

fix(email): route multi-mailbox message actions by recorded provenance (#1707)#1735
itomek merged 2 commits into
mainfrom
tmi/issue-1707-multimailbox-provenance

Conversation

@itomek

@itomek itomek commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Closes #1707

Before: with Gmail + Outlook both connected, acting on a message/thread id returned by search/list failed — Cannot determine which mailbox message '<id>' belongs to … multiple mailboxes are connected — because the id arrived without its source mailbox. Full-thread summary, batch archive, and summarize-message all broke multi-mailbox. After: list_inbox/search_messages scan every connected mailbox, tag each result with its source provider, and record provenance; summarize_message accepts an optional mailbox and routes via the recorded origin. Actions route deterministically with no mailbox=.

Proof it resolves #1707

Driving the real branch code with two mailbox backends:

search_messages   → id=g1 mailbox='google'  |  id=m1 mailbox='microsoft'
summarize_message('m1')  (no mailbox=)  → routed to the microsoft backend; google untouched

Full captured output + the unknown-id fail-loud case are in the evidence comment below.

Test plan

  • pytest tests/unit/agents/email/ tests/unit/email/453 passed (per-worktree venv pinned to this branch's code)
  • New test_multimailbox_provenance.py (13 tests): list/search tagging + provenance; summarize / summarize_thread / archive_message_batch route with no mailbox=; unknown id + 2 mailboxes fails loud
  • Optional live gaia email two-mailbox pass — deterministic code paths already proven above

Tomasz Iniewicz added 2 commits June 18, 2026 10:24
list_inbox/search_messages scan all connected mailboxes, tag each result with
its source provider, and record provenance; summarize_message accepts an optional
mailbox and routes via _backend_for_message. Fixes multi-mailbox summarize/archive
failing without an explicit mailbox=.
@github-actions github-actions Bot added the tests Test changes label Jun 18, 2026
@itomek

itomek commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Test evidence — multi-mailbox provenance

How it was tested — per-worktree venv pinned to this branch's code (verified gaia_agent_email.__file__ resolves into the branch worktree, not the shared stale install):

  • pytest tests/unit/agents/email/ tests/unit/email/453 passed
  • New tests/unit/agents/email/test_multimailbox_provenance.py (13 tests): list/search tag + remember provenance; summarize_message / summarize_thread / archive_message_batch route by recorded provenance with no mailbox=; unknown id + 2 mailboxes still fails loud.

Proof of implementation — driving the real branch code with two spy backends (OAuth boundary faked, routing logic real):

=== search_messages across BOTH connected mailboxes — each result tagged ===
  id=g1   mailbox='google'     subject='msg-g1'
  id=m1   mailbox='microsoft'  subject='msg-m1'
=== provenance recorded in agent (id -> mailbox) ===
   {'g1': 'google', 'm1': 'microsoft'}
=== summarize_message('m1') WITH NO mailbox= : routes to the microsoft backend ===
  google backend get_message calls  : []
  microsoft backend get_message calls: ['m1']

Before this PR, acting on an id surfaced by search/list raised Cannot determine which mailbox message '<id>' belongs to ... multiple mailboxes are connected.

@itomek itomek self-assigned this Jun 18, 2026
@itomek itomek marked this pull request as ready for review June 18, 2026 15:11
@itomek itomek requested a review from kovtcharov-amd as a code owner June 18, 2026 15:11
@itomek itomek enabled auto-merge June 18, 2026 15:14
@github-actions

Copy link
Copy Markdown
Contributor

Summary

This closes a real multi-mailbox gap: with Gmail + Outlook both connected, ids returned by list_inbox/search_messages arrived without their source provider, so any follow-up action (summarize_message, summarize_thread, batch archive) hit the "Cannot determine which mailbox …" wall. The fix scans every connected mailbox, tags each result with its mailbox provider, records provenance, and points summarize_message at _backend_for_message — exactly mirroring the established _triage_all_backends / _pre_scan_all_backends pattern (agent.py:460, :565). It's a tight, well-scoped change with strong test coverage including the fail-loud negative case. No blocking issues; a few minor points below.

One note on verification: I couldn't run the suite or formatters in the review sandbox (pytest/black/isort unavailable, read-only FS), so the findings are from static review against the author's reported 453 passed.

Issues Found

🟢 Budget under-fills when mailboxes are uneven (read_tools.py:756, :872)
per_backend = max_results // len(backends) is computed once and never redistributed. With two mailboxes and max_results=25, each gets 12; if Gmail only has 3 INBOX messages, the result caps at ~15 even when Outlook has plenty more to give. This matches the triage/pre-scan helpers, so it's consistent — but those carry a TOTAL-budget cap specifically because local inference is slow (~9–31 s/email). list_inbox/search_messages are cheap API reads with no such cost, so here the under-fill is a pure UX loss with no compensating benefit. Worth a follow-up to redistribute leftover budget to remaining backends.

🟢 next_page_token is now always dropped (read_tools.py:770, :885)
list_inbox hardcodes "next_page_token": None and search_messages omits it entirely, where the old single-backend path forwarded the impl's real token. Neither tool exposes a page_token argument today, so this is likely a no-op in practice — but if pagination was ever intended to be wired up, the merge makes it harder. Confirm this is intentional (cross-mailbox pagination genuinely is awkward to merge).

🟢 A weak test assertion can pass without verifying anything (test_multimailbox_provenance.py:355)
test_search_tags_per_source_mailbox guards its asserts with if "g1" in by_id: / if "m1" in by_id:. With the default budget both ids are always present, so the guards never trip — but that means if a regression dropped one of them, the test would silently pass instead of failing. Consider asserting both ids are present first, then asserting their tags.

🟢 Loop logic is now duplicated a fourth time (read_tools.py:755, :871)
The "split budget → call backend → tag mailbox → remember provenance → merge" loop now exists inline in both tool closures plus _triage_all_backends and _pre_scan_all_backends. The triage/pre-scan variants live as agent methods; these two are inlined. A small shared helper on the agent (e.g. _merge_across_backends(impl, **kwargs)) would keep the budget semantics in one place and match where the other two already live. Not blocking — just flagging before a fifth copy appears.

Strengths

  • Pattern fidelity. The new loops match _triage_all_backends exactly (total-budget split, mailbox tag, dual id+thread_id provenance), so the multi-mailbox behavior is uniform across list/search/triage/pre-scan.
  • Fails loud, per GAIA convention. summarize_message routes through _backend_for_message, which raises an actionable error for an untagged id with two mailboxes rather than guessing — and there's an explicit test (test_summarize_message_unknown_id_two_backends_fails_loud) pinning that behavior.
  • Test coverage maps directly to the bug. 13 tests cover both directions (Gmail and Outlook routing), explicit mailbox=, thread routing, batch archive, and the no-silent-guess negative case. The _Recorder shim in test_summarize_tools.py was correctly extended with _backends + _backend_for_message so the single-backend tests stay green.

Verdict

Approve with suggestions. No blocking issues — correctness and the fail-loud contract are sound, and coverage is genuinely good. The four 🟢 items (budget redistribution, dropped page token, the weak conditional assert, and the loop duplication) are all safe to address in this PR or a quick follow-up.

@itomek

itomek commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator Author

Live real-world evidence — real Gmail + Outlook (connected Mac, Lemonade Gemma-4-E4B)

Agent built from the real connectors (no injected backends); ids hashed and subjects/senders redacted (not posting mailbox content to a public PR):

connected backends: ['google', 'microsoft']
live list_inbox       → 12 messages  {'google': 6, 'microsoft': 6}   (each result tagged with its mailbox)
provenance recorded   → 15 entries (message + thread ids)
summarize_message(real Outlook id, NO mailbox=) → ok=True
summarize_message(real Gmail   id, NO mailbox=) → ok=True     (real Gemma-4-E4B summary)

Real OAuth + real Gmail/Graph APIs + real inference + real branch code. Pre-fix, the cross-mailbox id raised Cannot determine which mailbox '<id>' belongs to … multiple mailboxes are connected.

@kovtcharov-amd kovtcharov-amd left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean multi-mailbox provenance fix. list/search now tag each result with its source mailbox and record provenance, and summarize_message routes via _backend_for_message with an optional explicit mailbox — unknown id + multiple mailboxes still fails loud (no guessing). Confirmed removing the gmail = self._gmail capture in summarize_tools is safe (the remaining gmail refs are in the module-level summarize_message_impl, and there's only one registered tool in that closure). 13 new provenance tests.

Note (non-blocking): the one red check — "Test GAIA CLI on Linux (Full Integration)" — is an infra timeout in the "Install system dependencies" step (exceeded 15m before any test ran), unrelated to this change. All email-specific suites are green. LGTM.

@itomek itomek added this pull request to the merge queue Jun 18, 2026
Merged via the queue into main with commit aa6fa7e Jun 18, 2026
49 of 50 checks passed
@itomek itomek deleted the tmi/issue-1707-multimailbox-provenance branch June 18, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(email): multi-mailbox tool actions lose message provenance and fail when 2 mailboxes are connected

2 participants