This page shows real system architecture, but the demo below uses sample data — not a live view into my actual library account or email. ← Back to portfolio
Kindle Recommender Agent

Why I built this

Finding great book recommendations and manually managing library holds across platforms was a friction-heavy process. To automate my reading pipeline, I built a monthly autonomous agent that syncs with my Goodreads reading history, generates tailored recommendations, checks San Diego County Library availability, and automatically places holds — delivering a detailed email digest straight to my inbox alongside a local Streamlit dashboard to track past runs.

System Architecture

Kicks off two ways, then it's the same pipeline either way.

Monthly schedule
Fridays, 9:00 AM
macOS launchd
"Run now" button
On-demand, from the dashboard
Streamlit
run_monthly.sh
Launches a headless Claude Code run with the project's MCP server attached, and a fixed set of pre-approved tools
BashClaude Code CLI
Claude reasons over 5 MCP tools — decides what to read, recommend, hold, and say
get_reading_history
Scrapes my Goodreads shelves via a saved login session
PlaywrightBeautifulSoup
search_library_book
Checks SDCL/OverDrive availability + wait time
Playwright
place_library_hold
Clicks "place a hold," then re-checks the account's Holds page to confirm it actually registered
Playwright
save_recommendations
Writes this run's picks + reasoning to disk
JSON
send_digest_email
Sends the recommendations + reasoning as an HTML email
Gmail SMTP
data/*.json
One saved record per run
Email digest
Lands in my inbox
Local dashboard
Browses every past run, plus reading analytics charts pulled straight from Goodreads
StreamlitAltairPandas

Software Used

No hosted backend — everything runs locally and on a schedule.

Python Claude Code (headless) FastMCP Playwright BeautifulSoup Streamlit Altair Pandas Gmail SMTP macOS launchd

Try the Dashboard

Sample data below — this was my real recommendation in July.

Books read per year — sample data

Why verification matters

The initial version of place_library_hold considered a task complete the moment it clicked "place hold." However, UI clicks can fail silently without throwing an exception — meaning the agent could report a successful run while OverDrive never actually registered the hold. The workflow is updated to navigate directly to the account's Holds page after execution, verifying that the target title actually appears before confirming success.