Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/guides/email.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The Email Triage Agent connects to your Gmail account through GAIA's connectors
## What it does

- **Triage your inbox** — classify every message as `urgent`, `actionable`, `informational`, or `low priority`, plus separate `is_spam` and `is_phishing` flags.
- **Follow-up tracking** — flag the emails *you* sent that never got a reply, once they're older than a configurable window (default 3 days). Read-only detection: the agent surfaces the overdue threads, it never auto-sends a chaser.
- **Organize** — archive, label, mark read/unread, star/unstar. Reversible via the per-action undo log.
- **Soft-delete with undo** — `trash_message` records the action; `restore_message` reverses it within a 30-second window.
- **Draft + confirmed send** — generate replies (`draft_reply`) and forwards (`draft_forward`); `send_draft` and `send_now` require explicit user confirmation in the UI.
Expand Down Expand Up @@ -147,7 +148,9 @@ Senders you reply to quickly are automatically promoted to priority on the next

### Read

`list_inbox`, `get_message`, `get_thread`, `search_messages`, `list_labels`, `triage_inbox`, `pre_scan_inbox`
`list_inbox`, `get_message`, `get_thread`, `search_messages`, `list_labels`, `triage_inbox`, `pre_scan_inbox`, `find_awaiting_reply`

`find_awaiting_reply` ([#1606](https://github.com/amd/gaia/issues/1606)) scans your Sent folder and returns the threads whose newest message is still yours — i.e. nobody replied — once the send is older than the window (`window_days` per call, or the agent's `followup_window_days` config, default 3). Each hit carries the message id, recipient, subject, and age in days, most overdue first. Ask the agent *"who hasn't replied to me?"* or *"what am I still waiting on?"*. One scan inspects up to the 100 most-recent sent threads; when older sends exist beyond that, the result carries `scan_truncated: true` so the agent tells you the list may be incomplete rather than passing it off as exhaustive. Detection only — sending an actual follow-up goes through the normal confirmation-gated reply tools, and only when you ask ([#555](https://github.com/amd/gaia/issues/555) tracks autonomous nudging). Gmail-only for now: the Outlook backend has no Sent-label mapping yet, so a Microsoft-only setup gets a clear error instead of a wrong answer.

### Session preferences (in-memory; wiped on agent restart)

Expand Down
13 changes: 13 additions & 0 deletions hub/agents/npm/agent-email/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ follows [SemVer](https://semver.org/): the **MAJOR** of the on-the-wire
`SCHEMA_VERSION` is what `checkVersion` enforces at startup, so a contract MAJOR
bump is always at least a package MINOR bump with a migration note.

## Unreleased

- **Follow-up tracking — flag sent mail awaiting a reply** (#1606): the agent can
now scan the Sent folder and surface every thread whose newest message is still
the user's own — i.e. nobody replied — once the send is older than a configurable
window (`followup_window_days`, default 3 days, or per call). The new read-only
`find_awaiting_reply` agent tool returns message id, recipient, subject, and age,
most overdue first. Detection only: it never drafts or sends a nudge (autonomous
follow-up sending remains #555, confirmation-gated). Gmail-only for now — a
Microsoft-only setup gets a loud error instead of a silently wrong scan.
Agent-loop surface (Agent UI natural-language queries); no new REST route, so the
REST contract and `SCHEMA_VERSION` are unchanged.

## 0.3.0

Contract bumped to `SCHEMA_VERSION` **2.1** — additive, no triage shape change, so
Expand Down
7 changes: 5 additions & 2 deletions hub/agents/npm/agent-email/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ the agent runs as a frozen, self-contained REST sidecar your app launches and ow

- **Triage** — classify each message (urgent / needs-response / FYI / promotional /
personal), summarize a thread, and extract action items and phishing signals.
- **Follow-up tracking** — flag sent threads still awaiting a reply after a
configurable window (read-only detection; the agent never auto-sends a chaser).
- **Organize** — archive, label, move, mark read/unread — one message or in batches.
- **Reply & send** — draft context-aware replies and forwards, then send.
- **Calendar** — detect meeting requests, flag conflicts, RSVP, and create events
Expand Down Expand Up @@ -217,8 +219,9 @@ When you need finer control, the steps are exported individually:
As of `SCHEMA_VERSION` 2.1 this package exposes inbox **search** (read-only),
the **archive** / phishing-**quarantine** mailbox actions (+ their undo), and
calendar **view / create / respond**. The remaining mailbox **actions** (label,
move, mark read/unread) are part of the full agent but not yet exposed through
this package — see
move, mark read/unread) and the read-only **follow-up tracker** (flag sent mail
awaiting a reply, `find_awaiting_reply`, #1606) are part of the full agent but
not yet exposed through this package — see
[`SPEC.md`](https://github.com/amd/gaia/blob/main/hub/agents/npm/agent-email/SPEC.md) for the complete surface.

## Running in production
Expand Down
6 changes: 6 additions & 0 deletions hub/agents/npm/agent-email/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ reversible inside a 30s window via the ungated `unarchive` / `unquarantine`. Eve
non-2xx response throws `HttpError` (`status`, `url`, `bodyText`) — handle it; there is
no silent null.

The full agent (the natural-language loop behind GAIA's Agent UI) covers more than
this typed client: organize/label actions, calendar detect/conflicts, and read-only
**follow-up tracking** (`find_awaiting_reply` flags sent mail still awaiting a reply,
#1606). None of those have REST routes yet — don't look for them on this client; see
`SPEC.md` for the exact exposed surface.

## 5. From a renderer (Electron / browser)

The sidecar serves **same-origin only — no CORS**. A renderer on a different origin
Expand Down
7 changes: 4 additions & 3 deletions hub/agents/npm/agent-email/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,10 @@ phishing-**quarantine** mailbox actions plus their undo (`confirmAction` / `arch
`unarchive` / `quarantine` / `unquarantine`), and calendar **view / create / respond**
(`listCalendarEvents` / `previewCalendarEvent` / `createCalendarEvent` /
`respondToCalendarEvent`). The full GAIA email agent does more on the live mailbox
(label, move, mark spam) and calendar (detect / conflicts); those remaining actions are
connector-gated by definition and are **not exposed through this package's REST API
yet**.
(label, move, mark spam), follow-up tracking (flag sent mail still awaiting a reply —
read-only `find_awaiting_reply`, #1606), and calendar (detect / conflicts); those
remaining capabilities are connector-gated by definition and are **not exposed through
this package's REST API yet**.

## Browser / Electron renderer (`./client`)

Expand Down
9 changes: 8 additions & 1 deletion hub/agents/python/email/gaia_agent_email/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class never passes ``use_claude=True`` / ``use_chatgpt=True`` to
)
from gaia_agent_email.tools.calendar_tools import CalendarToolsMixin
from gaia_agent_email.tools.delete_tools import DeleteToolsMixin
from gaia_agent_email.tools.followup_tools import FollowupToolsMixin
from gaia_agent_email.tools.organize_tools import OrganizeToolsMixin
from gaia_agent_email.tools.phishing_tools import PhishingToolsMixin
from gaia_agent_email.tools.preference_tools import (
Expand Down Expand Up @@ -118,7 +119,11 @@ def __getattr__(self, name: str):

ACTIONS:
- Read tools (list_inbox, get_message, get_thread, search_messages,
list_labels, triage_inbox, pre_scan_inbox) — never require confirmation.
list_labels, triage_inbox, pre_scan_inbox, find_awaiting_reply) — never
require confirmation. find_awaiting_reply flags sent mail whose thread
never got a reply; it is DETECTION ONLY — never send a chaser from its
results unless the user explicitly asks, and any send stays
confirmation-gated.
- Organize tools (archive_message, mark_read, mark_unread, add_star,
remove_star, label_message, move_to_label) — reversible via the undo
log; do not require per-action confirmation, but bulk operations
Expand Down Expand Up @@ -167,6 +172,7 @@ class EmailTriageAgent(
MemoryMixin,
DatabaseMixin,
ReadToolsMixin,
FollowupToolsMixin,
OrganizeToolsMixin,
ReplyToolsMixin,
SummarizeToolsMixin,
Expand Down Expand Up @@ -349,6 +355,7 @@ def _register_tools(self) -> None:
_TOOL_REGISTRY.clear()
self._reset_organize_counter()
self._register_read_tools()
self._register_followup_tools()
self._register_organize_tools()
self._register_reply_tools()
self._register_summarize_tools()
Expand Down
9 changes: 9 additions & 0 deletions hub/agents/python/email/gaia_agent_email/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ class EmailAgentConfig:
default), tracks ``mail_provider`` so a Microsoft-only user who set
``mail_provider="microsoft"`` gets the Outlook calendar too without
separately configuring it.
- ``followup_window_days``: how many days a sent message may sit with no
inbound reply on its thread before ``find_awaiting_reply`` flags it
(#1606). Callers can override per call via the tool's ``window_days``.
- ``gmail_backend`` / ``outlook_backend`` / ``calendar_backend``: eval
seam — when set, the agent's tools use the injected backend instead of
constructing the live one. ``gmail_backend`` is honored for
Expand All @@ -122,6 +125,7 @@ class EmailAgentConfig:
outlook_backend: Optional[Any] = None
calendar_backend: Optional[Any] = None
force_llm: bool = False
followup_window_days: int = 3

def validate(self) -> None:
"""Run startup-time invariants. Called from the agent's __init__.
Expand All @@ -140,6 +144,11 @@ def validate(self) -> None:
"cloud LLM endpoints are permitted (AC3). To use a "
"non-default Lemonade port, set LEMONADE_BASE_URL."
)
if self.followup_window_days < 1:
raise ConfigurationError(
f"EmailAgentConfig.followup_window_days must be >= 1 "
f"(got {self.followup_window_days})."
)

def resolved_db_path(self) -> str:
"""Return the SQLite path with ``$HOME`` expanded.
Expand Down
Loading
Loading