feat(email): follow-up tracking — flag sent mail awaiting a reply#1916
feat(email): follow-up tracking — flag sent mail awaiting a reply#1916kovtcharov wants to merge 3 commits into
Conversation
) The dropped thread is the inbox's biggest silent failure mode: you send a question, nobody answers, and nothing resurfaces it. The agent can now scan the Sent folder and flag every thread whose newest message is still the user's own once it is older than a configurable window (followup_window_days, default 3 days, or per call) via the new read-only find_awaiting_reply tool — message id, recipient, subject, and age, most overdue first. Detection only, per the #555 boundary: the module imports no send path (the unit tests assert both the module source and the backend transport calls stay read-only), and any actual chaser goes through the confirmation-gated reply tools at the user's request. Gmail-only for now — the Graph backend serves the inbox folder for unrecognized labels, so a Microsoft-only setup gets a loud refusal instead of a silently wrong scan.
|
Verdict: Approve with suggestions — clean, well-tested, well-documented feature. This adds read-only follow-up tracking to the email agent: a new One thing worth a look before merge: the scan only inspects the newest 100 sent messages. Because that list is newest-first, a thread whose last send was months ago and has since been buried past the 100 most-recent sends will never be inspected — which is exactly the "dropped thread you forgot you sent" the feature is meant to catch. For a heavy sender the answer to "what am I still waiting on?" could be quietly incomplete. Not a blocker (the bound is a reasonable interactive ceiling and 🔍 Technical details🟡 ImportantNewest-100 truncation can hide the most-overdue threads ( 🟢 Minor
Strengths
|
TestToolRegistry.test_no_unexpected_tool_set guards against tools that bypass confirmation logic; the new read-only follow-up tracker belongs in its expected set.
…haustiveness The Sent listing is newest-first and capped at 100 stubs, so a heavy sender's oldest — most overdue — threads can fall outside one scan. The result now carries scan_truncated whenever a ceiling was hit (next page token, full listing page, or more threads than max_threads), the tool docstring tells the LLM to relay the incompleteness, and the max_threads cap is tied to DEFAULT_SENT_SCAN_CEILING so the two limits can't drift apart. Raised in the PR #1916 review.
|
Both review points addressed in 6e16e60. The result now carries |
The unanswered email you forgot you sent is the inbox's biggest silent failure mode — before this, a dropped thread simply disappeared. Now you can ask the email agent "who hasn't replied to me?" and the new read-only
find_awaiting_replytool scans the Sent folder and surfaces every thread still waiting on a response past a configurable window (default 3 days): message id, recipient, subject, and age, most overdue first. Detection only — it never drafts or sends a nudge (autonomous follow-up sending stays with #555, confirmation-gated), and the tests assert the detector touches no send path at all.Scope notes for the reviewer:
POST /v1/email/query); no new REST route, so the REST contract andSCHEMA_VERSIONare unchanged. A deterministic fixed-function route per the feat(email): expose search, pre-scan, archive/quarantine & calendar on the REST contract (schema 2.1) #1883 pattern can ride a follow-up if wanted.specification.htmlcapability matrix updated to match.Test plan
python -m pytest tests/unit/agents/email/ tests/unit/email/ hub/agents/python/email/tests/— 729 passed (the one failure,test_agent_version_matches_package_metadata, is pre-existing local-venv metadata skew and fails identically on clean main)tests/unit/agents/email/test_followup_tracking.py(14 tests) locks the feat(email): follow-up tracking — flag sent mail awaiting a reply #1606 acceptance criteria: replied thread NOT flagged; unreplied flagged only past the window; latest-send-only flagging; nosend_*/draft side effects (transport log + module source); fail-loud on empty user email, bad window, unparseableinternalDate; Microsoft-only refusal; config-window wiring through the registered toolpython util/lint.py --all --fixcleanCloses #1606