feat(email): scheduled daily inbox briefing (#1608)#1923
Conversation
The inbox pre-scan only ran when a user asked for it. Now a host scheduler can turn it into a daily morning briefing: the sidecar persists an off-by-default schedule (GET/PUT /v1/email/briefing/schedule) and serves a trigger (POST /v1/email/briefing/run) that reuses pre_scan_inbox_impl and returns a kind: email_briefing envelope wrapping the same email_pre_scan card, also persisting it to ~/.gaia/email/briefing_latest.json as the interim pull-based delivery surface until push delivery lands with the autonomy engine (#555). The sidecar deliberately runs no timer — scheduling stays with the host (autonomy engine, cron, the GAIA UI scheduler), keeping the seam to #555 a single documented trigger. The trigger re-checks enabled itself and answers 409 before any mailbox access, so a stale or misfiring scheduler can never scan a mailbox whose briefing is off. A corrupt schedule file is a 500 naming the file and the fix, never a silent fall-back to disabled. Contract SCHEMA_VERSION bumps 2.1 -> 2.2 (additive, same MAJOR, so 2.x npm clients keep working; TS const follows). The new endpoints are REST-only for now — typed client methods land with the autonomy-engine integration.
|
Verdict: Approve ✅ This adds an off-by-default scheduled inbox briefing to the email sidecar: three REST endpoints ( Documentation is synced across every surface the CLAUDE.md rule cares about — README, SPEC, SKILL, CHANGELOG, One low-severity note below (a concurrent- 🔍 Technical detailsCorrectness verified
🟢 Minor — concurrent
|
Closes #1608.
The inbox pre-scan only answered when a user asked for it — there was no way to get the morning briefing #1608 describes without typing a prompt. Now the email sidecar carries an off-by-default briefing schedule and a scheduled trigger: a host scheduler fires
POST /v1/email/briefing/rundaily and gets back akind: "email_briefing"envelope wrapping the exactemail_pre_scancard the Agent UI already renders (reusingpre_scan_inbox_impl— no re-implemented scan), also persisted to~/.gaia/email/briefing_latest.jsonso a consumer can pull the latest briefing without having been the live caller.Scope note (per the issue's autonomy-engine dependency): the sidecar deliberately runs no timer and push delivery has no home until #555 lands — the seam to the autonomy engine is the single documented trigger plus
GET/PUT /v1/email/briefing/schedule. The trigger re-checksenableditself and answers 409 before any mailbox access, so a stale or misfiring scheduler can never scan a mailbox whose briefing is off; a corrupt schedule file is a 500 naming the file and the fix, never a silent fall-back to disabled. ContractSCHEMA_VERSIONbumps 2.1 → 2.2 (additive, same MAJOR — existing 2.x npm clients keep working; the new endpoints are REST-only for now, typed client methods land with the autonomy-engine integration). No LLM-affecting path changes (no prompts/tools/parsing touched), so no eval run is required.Test plan
python -m pytest hub/agents/python/email/tests/ tests/unit/agents/email/— 630 passed (includes the two issue ACs intest_email_briefing.py: scheduled job →email_pre_scanenvelope; disabled schedule → no briefing and the mailbox backend is never touched)npx vitest runinhub/agents/npm/agent-email— 59 passed (TSSCHEMA_VERSION2.2)python util/lint.py --all— cleanpackaging/server.pyon an ephemeral port):/versionreports 2.2; schedule defaults disabled;run→ 409 while disabled;PUTround-trips and persists; badtime→ 422git grep -n briefing hub/agents/npm/agent-email— README/SPEC/SKILL/CHANGELOG,specification.html,openapi.email.json(regenerated), anddocs/guides/email.mdxall describe the same off-by-default / host-owns-the-timer semantics