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
37 changes: 36 additions & 1 deletion docs/guides/email.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,46 @@ Email-body inference runs on your local Lemonade instance. The agent **rejects**
| `-v, --verbose` | Emit structured logs for every triage decision and tool call. Recommended when benchmarking against other email agents. |
| `--debug` | Adds full prompt + LLM-response logging to verbose. Sensitive payloads in logs — use with care. |

## Daily-driver pre-scan (Agent UI)

The Agent UI rendering of Email Triage is built around a **pre-scan** view — a structured triage card that surfaces what's worth your attention without making you read prose. Open Agent UI, pick **Email Triage** from the agent picker, and click the **"Run a pre-scan"** conversation starter (or just type it).

The card shows three sections:

- **Urgent** — messages that need your attention right now (top 5).
- **Needs a response** — messages requiring a reply or decision (top 5).
- **Suggested archives** — low-priority messages the agent recommends archiving (top 10).

Plus an **informational count** for the rest, so you know how much you're not seeing.

Each row carries inline action buttons:

- **Reply / Archive** (primary) — Reply for urgent + actionable rows; Archive for suggested-archive rows. Clicking dispatches the corresponding tool call back through the chat (with confirmation when the action requires it).
- **Open** — open the message in Gmail in a new tab.
- **Dismiss** — remove the row from the visible card without affecting Gmail.

If you haven't connected Google yet, the agent surfaces a one-click **Connect Google** button inline in the chat — no need to navigate to Settings → Connections manually.

### In-session preferences (in-memory, wiped on restart)

Tell the agent how you want classification to behave for this session:

- *"Treat boss@company.com as urgent"* → calls `set_priority_sender`. That sender bypasses the heuristic and lands in **Urgent** for the rest of the session.
- *"Treat newsletter@stripe.com as low priority"* → calls `set_low_priority_sender`. That sender lands in **Suggested archives**.
- *"Default informational mail to archive"* → calls `set_category_default("informational", "archive")`. Informational items lift into **Suggested archives** until you reset.
- *"Clear my preferences"* → calls `clear_session_preferences`.

Preferences are stored in process memory only — restarting the agent (or quitting Agent UI) wipes them. This is deliberate: the goal is to prove the value of session-scoped learning before we wire up persistent memory. Once persistent memory ships, the same tools will write through to it without changing this surface.

## Action surface

### Read

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

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

`set_priority_sender`, `set_low_priority_sender`, `set_category_default`, `clear_session_preferences`

### Organize (reversible via the undo log)

Expand Down
29 changes: 28 additions & 1 deletion src/gaia/agents/email/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class never passes ``use_claude=True`` / ``use_chatgpt=True`` to
from gaia.agents.email.tools.calendar_tools import CalendarToolsMixin
from gaia.agents.email.tools.delete_tools import DeleteToolsMixin
from gaia.agents.email.tools.organize_tools import OrganizeToolsMixin
from gaia.agents.email.tools.preference_tools import (
PreferenceToolsMixin,
init_session_preferences,
)
from gaia.agents.email.tools.read_tools import ReadToolsMixin
from gaia.agents.email.tools.reply_tools import ReplyToolsMixin
from gaia.connectors.providers.base import ConnectorRequirement
Expand Down Expand Up @@ -88,7 +92,7 @@ class never passes ``use_claude=True`` / ``use_chatgpt=True`` to

ACTIONS:
- Read tools (list_inbox, get_message, get_thread, search_messages,
list_labels, triage_inbox) — never require confirmation.
list_labels, triage_inbox, pre_scan_inbox) — never require confirmation.
- 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 All @@ -100,6 +104,20 @@ class never passes ``use_claude=True`` / ``use_chatgpt=True`` to
create_event_from_email) — REQUIRE explicit user confirmation. The UI
shows the user the literal recipient/subject/body; trust ONLY what
appears there.
- Preference tools (set_priority_sender, set_low_priority_sender,
set_category_default, clear_session_preferences) — mutate session-scoped
classification preferences. Confirm the change in plain English; the
preferences are wiped on agent restart by design.

PRE-SCAN BEHAVIOR:
When the user asks for a pre-scan, morning brief, triage view, or "what's
in my inbox", call ``pre_scan_inbox``. The chat surface renders a
structured triage card automatically from the tool's return value — you
do NOT need to copy the JSON into your reply. After the tool returns,
write ONE short framing sentence (e.g. "Here's your inbox pre-scan — 5
actionable, 1 suggested archive.") and stop. The user can see the card;
do not re-state its contents in prose. For follow-up questions about
specific items, refer to the message_id values from the card.

OUTPUT:
Tool results come back as JSON envelopes ``{"ok": true, "data": ...}``
Expand All @@ -121,6 +139,7 @@ class EmailTriageAgent(
ReplyToolsMixin,
DeleteToolsMixin,
CalendarToolsMixin,
PreferenceToolsMixin,
):
"""Email Triage Agent — Gmail + Calendar through the connectors
framework, all body inference local on Lemonade.
Expand All @@ -141,6 +160,7 @@ class EmailTriageAgent(
"locally on your machine."
)
CONVERSATION_STARTERS: ClassVar[List[str]] = [
"Run a pre-scan",
"Triage my inbox",
"Summarize my unread emails",
"Draft a reply to my most recent message",
Expand Down Expand Up @@ -182,6 +202,12 @@ def __init__(self, config: Optional[EmailAgentConfig] = None):
self._organize_op_count = 0
self._organize_distinct_senders: set[str] = set()

# Session-scoped triage preferences — sender priorities and
# category defaults that survive across queries within one agent
# instance and are wiped on restart. See ``preference_tools.py``
# for the schema and the tools that mutate this state.
self._session_preferences = init_session_preferences()

# SQLite for the action log. Default ``~/.gaia/email/state.db``.
# Eval / unit tests inject ``db_path=tmp_path/state.db``.
db_path = config.resolved_db_path()
Expand Down Expand Up @@ -230,6 +256,7 @@ def _register_tools(self) -> None:
self._register_reply_tools()
self._register_delete_tools()
self._register_calendar_tools()
self._register_preference_tools()

# -- Phase I3 batch-organize counter -----------------------------------

Expand Down
Loading
Loading