Add research digest: POST /research sends Gemini-powered email on any topic - #30
Add research digest: POST /research sends Gemini-powered email on any topic#30krMaynard wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new research digest feature that uses Gemini with Google Search grounding to research a configured topic and email the summary directly to the user. Key changes include a new /research endpoint, a researcher module, and a helper to send emails immediately via the Gmail API. The review feedback highlights three important improvements: handling missing environment variables gracefully with a 400 Bad Request, adding defensive checks for empty or blocked Gemini responses, and explicitly specifying UTF-8 encoding when creating MIME text to prevent encoding errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Codex reviewFixed one concrete configuration regression in Validation: deterministic suite — 2 passed; One product-level blocker remains before merge: |
…ny topic Uses Gemini 2.0 Flash with Google Search grounding to research a configurable topic and sends the summary directly to the authenticated Gmail account. Triggered by Cloud Scheduler on whatever cadence the user configures. New: assistant/researcher.py, send_message() in email_reader.py, /research route in main.py. README updated with setup instructions and env var docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSnZNqUEh8uDyMCPDmzBTa
- Return 400 Bad Request when RESEARCH_TOPIC env var is not set - Raise ValueError on empty/blocked Gemini response in research_topic() - Use MIMEText(..., "plain", "utf-8") in send_message() to handle non-ASCII Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSnZNqUEh8uDyMCPDmzBTa
…etry POST /research sent immediately but recorded no completed run, so a Cloud Scheduler retry after a timeout could send the same dated digest twice. Mirror the /standup dedup pattern: persist the last sent (topic, date) to last_research.json in GCS and skip if today's digest for the current topic already went out. A changed RESEARCH_TOPIC still sends a fresh digest. Also use the get_user_email() helper (with its HttpError handling) instead of a raw getProfile call. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSnZNqUEh8uDyMCPDmzBTa
ba8d973 to
4b93cf5
Compare
|
Addressed the two remaining items from the Codex review:
Full suite: 21 passed, 8 skipped (Gemini eval tests skip without a key locally). Generated by Claude Code |
|
Codex re-review at Validation: deterministic suite 23 passed; |
Summary
POST /researchendpoint that uses Gemini (the operator-configuredGEMINI_MODEL) with Google Search grounding to research a configurable topic, then sends the summary directly to your Gmail inboxassistant/researcher.pymodule withresearch_topic()— single Gemini call with live web search, 2048 token cap, rejects empty/blocked responsessend_message()inemail_reader.py— uses existinggmail.modifyscope (no re-auth needed), UTF-8 encodedRESEARCH_TOPICenv var controls what to research (e.g."AI and LLM news this week"); missing topic returns400(topic, date)is recorded inlast_research.jsonin GCS, so a Cloud Scheduler retry after a timeout won't send the same digest twice on the same day (mirrors the/standupdedup pattern). Changing the topic sends a fresh digestTest plan
RESEARCH_TOPICenv var and hitPOST /researchlocally — confirm email arrives in inboxPOST /researchtwice in a row — confirm the second returns{"skipped": true, "reason": "already_sent_today"}/researchResearch email sent to ...🤖 Generated with Claude Code
https://claude.ai/code/session_01CSnZNqUEh8uDyMCPDmzBTa