Autonomous AI briefing agent in Go — reads the research so you don't have to.
Beacon is a self-hosted agent that monitors academic paper sources and tech blogs, detects your current energy level via Spotify, and delivers mood-aware briefings to your inbox twice a day at 9 am and 9 pm.
If you're at the gym when it fires, you get crisp 5–8 line summaries. If you're having a calm morning, you get full structured analyses with TL;DR previews.
Built as a portfolio project for AI/ML graduate school applications.
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ ┌─────────┐
│ Paper sources│───▶│ Mood detector│───▶│ Claude API │───▶│ Email │
│ (ArXiv, HF, │ │ (Spotify │ │ (generates │ │ (Resend)│
│ blogs, etc) │ │ genres) │ │ summaries) │ │ │
└──────────────┘ └──────────────┘ └──────────────────┘ └─────────┘
9 am run:
HIGH_BPM(gym, rap, metal, reggaeton…) → short 5–8 line summariesNORMAL(lo-fi, jazz, classical, nothing playing…) → full structured analysis
9 pm run: same logic applied to a fresh batch of papers
Mood classification is non-fatal — if Spotify is unreachable or nothing is playing, it defaults to NORMAL.
| Topic | Sources |
|---|---|
| AI / ML | ArXiv cs.AI, cs.LG, cs.CL · HuggingFace Papers |
| Healthcare AI | ArXiv cs.AI + q-bio · Semantic Scholar |
| Brain-Computer Interfaces | ArXiv eess.SP · bioRxiv |
| Computer Vision | ArXiv cs.CV |
| Bioengineering | bioRxiv · medRxiv |
| Anthropic / Claude | ArXiv cs.AI · HuggingFace Papers |
| Blogs | Simon Willison · Towards Data Science · Radical Data Science |
- Go 1.23+
- Spotify account with a registered app (free tier works)
- Anthropic API key — console.anthropic.com
- Resend account (free tier: 100 emails/day) — resend.com
- Optional: Last.fm API key for genre fallback when Spotify returns empty genres
git clone https://github.com/joako/beacon
cd beacon
go build -o bin/beacon ./cmd/beaconcp .env.example .envFill in .env with your credentials (see Environment variables below).
Beacon needs a long-lived refresh token to query Spotify without user interaction. Run the OAuth helper once:
go run scripts/spotify-auth.goFollow the browser prompt, then paste the resulting SPOTIFY_REFRESH_TOKEN into .env.
# Verify Spotify connection and mood detection
go run cmd/beacon/main.go --cmd=mood
# Fetch papers from a topic (no Claude, no email)
go run cmd/beacon/main.go --cmd=fetch --topic=AI
go run cmd/beacon/main.go --cmd=fetch --topic=BLOGS
# Full dry-run: fetch papers + Claude analysis, print to terminal, no email sent
# Set MOCK_MOOD to test both briefing formats without needing Spotify
MOCK_MOOD=NORMAL go run cmd/beacon/main.go --cmd=dry-run --hour=9 --papers=3
MOCK_MOOD=HIGH_BPM go run cmd/beacon/main.go --cmd=dry-run --hour=21 --papers=3# Start the production cron loop (blocks, fires at 9 am and 9 pm local time)
go run cmd/beacon/main.go --cmd=runOr build and run the binary:
go build -o bin/beacon ./cmd/beacon
./bin/beacon --cmd=run| Variable | Required | Description |
|---|---|---|
SPOTIFY_CLIENT_ID |
✅ | OAuth app client ID from Spotify Dashboard |
SPOTIFY_CLIENT_SECRET |
✅ | OAuth app client secret |
SPOTIFY_REFRESH_TOKEN |
✅ | Long-lived token (generated by scripts/spotify-auth.go) |
ANTHROPIC_API_KEY |
✅ | API key from console.anthropic.com |
RESEND_API_KEY |
✅* | Email delivery via Resend |
SENDGRID_API_KEY |
✅* | Email delivery via SendGrid (alternative to Resend) |
DELIVERY_EMAIL_TO |
✅ | Recipient email address |
LASTFM_API_KEY |
➖ | Genre fallback — free key at last.fm/api |
TIMEZONE |
➖ | IANA timezone for cron (default: America/Merida) |
LOG_LEVEL |
➖ | debug / info / warn / error (default: info) |
DRY_RUN |
➖ | true to print briefings without sending (default: false) |
MOCK_MOOD |
➖ | HIGH_BPM or NORMAL — overrides Spotify for testing |
*Either RESEND_API_KEY or SENDGRID_API_KEY is required, not both.
beacon --cmd=<command> [flags]
Commands:
run Start the production scheduler (default)
Fires at 09:00 and 21:00 in TIMEZONE
dry-run Run one pipeline cycle without sending email
--hour Hour to simulate: 9 or 21 (default 9)
--papers Max papers to process (default 3, saves API credits)
mood Print current Spotify track and detected MoodLevel
fetch Fetch and print papers from one topic
--topic AI | HEALTHCARE | BCI | CV | BIO | ANTHROPIC | BLOGS
--limit Max papers to return (default: unlimited)
--json Output as JSON array
Examples:
# Simulate a HIGH_BPM morning without real Spotify
MOCK_MOOD=HIGH_BPM go run cmd/beacon/main.go --cmd=dry-run --hour=9 --papers=5
# Fetch and inspect raw paper data as JSON
go run cmd/beacon/main.go --cmd=fetch --topic=BLOGS --json
# Check what mood Beacon currently detects
go run cmd/beacon/main.go --cmd=moodHIGH_BPM — concise, scannable while you're on the move:
📄 UniGRPO: Unified Policy Optimization for Visual Generation
🏷️ Wang et al. · arxiv · 2025-03-28
⚡ Introduces a unified GRPO variant for text-to-image models that stabilizes
training across reward types. Achieves +4.1 FID improvement over baselines
on COCO while reducing training steps by 30%. Key insight: separating the
policy update for visual vs. language tokens prevents reward hacking.
🔗 https://arxiv.org/abs/2503.xxxxx
---
NORMAL — structured analysis with full context:
📄 UniGRPO: Unified Policy Optimization for Visual Generation
🏷️ Wang et al. · arxiv · 2025-03-28
💡 TL;DR: Extends GRPO to visual generation, fixing training instability
with a novel token-separated policy update.
**Motivation:** Existing RL-based fine-tuning for image models suffers from
reward hacking when applying GRPO naively across visual and language tokens...
**Method:** ...
**Results:** ...
**Implications:** ...
**Caveats:** ...
🔗 https://arxiv.org/abs/2503.xxxxx
---
Mood is determined by the genres of your currently playing Spotify track.
HIGH_BPM genres (triggers summary mode):
gym · workout · rap · hip-hop · trap · drill · corridos · corridos tumbados · sierreño · banda · reggaeton · dembow · latin trap · hard rock · heavy metal · drum and bass · hardstyle · gabber
Chill modifiers (override high-energy classification even if genre matches):
lo-fi · lofi · chill · ambient · jazz · soul · cloud · sleep · study · house
So lo-fi hip-hop → NORMAL, jazz rap → NORMAL, but plain rap → HIGH_BPM.
Defaults:
- Nothing playing →
NORMAL - Spotify unreachable →
NORMAL(non-fatal)
beacon/
├── cmd/beacon/main.go # CLI entry point
├── internal/
│ ├── config/ # Env loading and validation
│ ├── mood/ # Spotify client + genre classifier
│ ├── papers/ # Multi-source paper fetcher
│ │ ├── arxiv.go
│ │ ├── huggingface.go
│ │ ├── semantic_scholar.go
│ │ ├── biorxiv.go
│ │ └── rss.go # Generic RSS/Atom fetcher (blogs)
│ ├── briefing/ # Claude API generator + formatter
│ ├── delivery/ # Email sender (Resend)
│ ├── store/ # In-memory paper store
│ └── scheduler/ # 9am/9pm cron state machine
└── .env.example # Environment variable template
go test ./...
# With verbose output
go test ./... -v
# A specific package
go test ./internal/papers/... -v -run TestRSSCoverage across internal/ packages is maintained above 85%.
- All secrets are loaded from environment variables — nothing hardcoded.
- HTTP clients enforce HTTPS-only URLs at construction time (panics on HTTP).
- All HTTP response bodies are capped with
io.LimitReader. DRY_RUN=trueis enforced in all delivery paths before any network call.- No secrets appear in log output — only boolean flags (e.g.
api_key_set=true).
| Component | Technology |
|---|---|
| Language | Go 1.23 |
| AI / summarization | Claude API (claude-sonnet-4-20250514) |
| Mood detection | Spotify Web API |
| Paper sources | ArXiv · HuggingFace · Semantic Scholar · bioRxiv/medRxiv · RSS |
| Email delivery | Resend |
| Logging | log/slog (structured) |
| External deps | godotenv only |
MIT