feat(email): extract action items into a persistent task list#1917
feat(email): extract action items into a persistent task list#1917kovtcharov wants to merge 1 commit into
Conversation
Triage returned action_items inline-only, so commitments extracted from email evaporated with the response. The REST triage endpoints now also write each extracted item to a persistent email_tasks table (the same SQLite as the action log), linked back to the source message_id and de-duplicated per message on the normalized description — re-triaging a message never duplicates tasks. Additive: the wire response, contract, and SCHEMA_VERSION are unchanged; results without a message_id are not persisted. The cross-agent L8 task store (#1521) is not in-tree yet, so the store is email-local: task_store.record_action_items is the single seam that becomes the adapter forwarding tasks (source_ref=message_id) when it lands. MCP stdio triage stays analyze-only for now — persistence is wired at the REST surface the Agent UI drives.
|
Verdict: Approve with suggestions — one edge-path reliability issue worth fixing before merge. This PR persists triage action items into a local The one thing to address: the new persistence runs after the triage result is computed but its errors aren't isolated. If a task write fails, a successful triage now returns a 500 instead of the result — and for the batch endpoint a single item's write failure collapses the entire per-item-isolated response into one 500, breaking the documented "per-item failures isolate" contract. The most concrete trigger is two overlapping triages of the same message racing on the 🔍 Technical details🟡 ImportantPersistence errors aren't isolated from the triage response — breaks batch per-item isolation (
The most realistic trigger is a race, not disk failure: Cleanest targeted fix — treat the That handles the race for both endpoints. Separately, the batch loop should keep a single item's persistence failure from sinking the whole response — the batch already isolates per-item triage failures, and the side-effect should match that (e.g. persist per-item under a narrow guard that logs and continues, rather than letting it propagate past 🟢 Minor
Strengths
|
Action items extracted during triage were inline-only — useful in the moment, gone with the response, so commitments buried across threads silently slipped. Now every
POST /v1/email/triage/triage/batchalso persists each extracted item as a task row in the sidecar's local SQLite, linked back to the sourcemessage_idand de-duplicated per message, so re-triaging never duplicates a task. Fully additive: the wire response, contract, andSCHEMA_VERSIONare unchanged.The L8 cross-agent task store (#1521) isn't in-tree yet, so tasks persist to an email-local
email_taskstable;task_store.record_action_itemsis the single seam that becomes the adapter forwarding tasks (source_ref=message_id) when that EPIC lands. MCP stdio triage stays analyze-only for now — persistence is wired at the REST surface the Agent UI drives (wiring the MCP subprocess needs a test seam likeGAIA_EMAIL_MCP_FAKE_SEND; noted as follow-up).Closes #1605
Test plan
python -m pytest hub/agents/python/email/tests/test_email_task_store.py -v— store unit tests (create/link, no-dup on re-record, normalized dedup key, fail-loud on missing message_id) + REST surface tests (triage persists linked tasks, re-triage creates no duplicates, batch dedups per message)python -m pytest hub/agents/python/email/tests/ tests/test_email_openapi_conformance.py tests/unit/agents/email/ tests/mcp/test_email_mcp_stdio_parity.py tests/test_api.py— 733 passed (contract freshness, MCP parity, existing triage behavior unchanged)python util/lint.py --all— clean