Skip to content

Latest commit

 

History

History
148 lines (111 loc) · 9.19 KB

File metadata and controls

148 lines (111 loc) · 9.19 KB

Palmer

The AI that texts like a friend who actually pays attention.

Palmer is a personal AI delivered entirely over SMS. No app to download, no interface to learn — just text it. It knows who you are, remembers what you care about, and reaches out when something worth knowing happens.


What Palmer does

Knows you

Every exchange teaches Palmer something new. Your city, your job, your teams, your interests — it builds a picture of you and uses it the way a real friend would: casually, in context, without citation. The longer you text, the sharper it gets.

Sends your morning

Each morning Palmer texts you a short, personalized briefing on what you actually care about — weather, local traffic, markets, sports, news. Not a newsletter. One text, just the things that matter, from today.

Watches for things

Tell Palmer to watch for something — a geopolitical event, a company move, an athlete's health update, anything — and it runs that in the background. When it hits, you get a text. No feed to check.

Sets reminders

Natural-language reminders that arrive when you need them. "Remind me Friday morning to prep for the meeting." Done.

Sends alerts

When something massive breaks in an area you care about, Palmer texts you before you'd think to check. Score threshold is high — it texts when it's actually worth knowing, not for every update.

Follows up

If you mentioned an interview, a doctor's visit, a rough week — Palmer notices. It circles back a day or two later to ask how it went. Not scripted check-ins; it picks the thread worth pulling on.

Sees photos

Send Palmer a picture and it'll actually respond to what's in it — a menu, a whiteboard, a receipt, a dog. It's using vision, not guessing from a filename.

Answers anything

Crypto prices, stock quotes, weather forecasts, sports scores, current events, city traffic, drive times with live traffic — all through the same text thread, no switching apps.

Stays out of the way

Tell Palmer to pause the morning texts, drop a topic, forget a fact about you, or change when it sends — all by texting. No settings screen.


Personality

Palmer is dry, quick, and observant. It's not an assistant and it's not a brand — it's a voice. It reads subtext, matches your energy, calls out patterns, and knows when to drop a GIF. Built on Claude, it converses like a person, not a product.


Stack

Layer Technology
Conversation + reasoning Claude Sonnet 4.6
Fast inference (extraction, scoring) Claude Haiku 4.5
SMS/MMS Twilio
Web server FastAPI on Heroku
News + web search Tavily
Weather NWS (US), Open-Meteo (elsewhere)
Traffic + routing TomTom (Traffic Flow, Traffic Incidents, Routing, Search geocoding)
Product prices + sale watches SerpAPI Google Shopping
Crypto prices CoinGecko
Stock prices yfinance
GIFs Giphy
Background jobs APScheduler (reminders 1m · mornings 5m · watches 30m · alerts 60m · missing-data asks 60m · follow-ups 4h · price watches 6h)
Database Heroku Postgres

Running Palmer

1. Clone and install

git clone https://github.com/pythonjeff/Palmer.git
cd Palmer
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt

2. Environment

cp .env.example .env
# Fill in the variables below
Variable Where to get it
ANTHROPIC_API_KEY console.anthropic.com
TWILIO_ACCOUNT_SID twilio.com dashboard
TWILIO_AUTH_TOKEN twilio.com dashboard
TWILIO_PHONE_NUMBER Your Twilio number (+15551234567)
TAVILY_API_KEY app.tavily.com
GIPHY_API_KEY developers.giphy.com (free)
TMDB_API_KEY themoviedb.org (free, non-commercial) — Opening section
TICKETMASTER_API_KEY developer.ticketmaster.com (free, 5k calls/day) — Opening section
TOMTOM_API_KEY developer.tomtom.com (free tier, ~2,500 requests/day)
SERP_API_KEY serpapi.com (free plan is 250 searches/mo; paid from $50/mo for 5,000)
APP_URL Your deployed app URL (for Twilio status callbacks)
DATABASE_URL Postgres connection string (auto-set by Heroku)

3. Run locally

uvicorn main:app --reload

4. Expose for Twilio (local dev)

ngrok http 8000
# Set Twilio webhook → https://<ngrok-url>/sms (POST)

Deploy to Heroku

heroku create
heroku addons:create heroku-postgresql:essential-0
heroku config:set ANTHROPIC_API_KEY=... TWILIO_ACCOUNT_SID=... TWILIO_AUTH_TOKEN=... \
  TWILIO_PHONE_NUMBER=... TAVILY_API_KEY=... GIPHY_API_KEY=... \
  TOMTOM_API_KEY=... APP_URL=...
heroku config:set WEB_CONCURRENCY=1
git push heroku main

Set your Twilio SMS webhook to https://<your-app>.herokuapp.com/sms (POST).
Set your Twilio status callback to https://<your-app>.herokuapp.com/sms-status (POST).

Morning briefings are sent by APScheduler inside the web dyno (checked every 5 minutes) at each user's chosen local time — 7am by default, changeable by texting Palmer. No Heroku Scheduler job is required; if one exists running python send_morning.py, it's a harmless redundant backup (the per-user sent-date guard prevents double-sends).


Preview endpoints

GET /preview?phone=+15551234567         # generate morning briefing without sending

Architecture

  • WEB_CONCURRENCY=1 is required. In-memory phone locks and APScheduler only work correctly in a single process.
  • Per-phone threading.Lock serializes inbound messages so conversation history never interleaves under concurrent requests from the same number.
  • Source quality is one gate, applied at the search call (sources.py). Every news fact and link Palmer sends — watch alerts, the morning briefing, Palmer Home, and conversational web search — goes through datafeeds._search_raw / _search, which filter before returning, so no surface can drift to its own standard. Four gates: a blocklist dropping press-release wires (prnewswire, globenewswire) and republishing aggregators (msn.com, biztoc, news.google.com) outright; a relevance floor that gives trusted sources 0.15 of slack, because Tavily's score measures query-text match and that is exactly what an SEO content farm is built to win; tier ordering from trusted_sources.json (tier 1 = wires and premier newsrooms plus .gov/.edu, tier 2 = mainstream and reputable specialists, tier 3 = everything else) sorting (tier, -score) so a wire report beats a higher-scoring blog; and corroboration requiring ≥ 2 distinct domains OR ≥ 1 tier-1 source before any unprompted alert fires. Palmer Home additionally drops tier 3 entirely — an untrusted row is worse than no row on a short curated list. Edit trusted_sources.json to add or remove sources; no code change needed.
  • Watches run every 30 minutes via APScheduler but only alert on major breaking developments — dated results from the last 12 hours, a strict criticality gate, a HEAD reachability check so a dead top link falls through to the next result, per-watch cooldown (default 4 hours), and dedup against the last alerted event.
  • Traffic uses TomTom. Morning briefings auto-include a short city snapshot (get_city_traffic): geocode → parallel Traffic Flow + Traffic Incidents in a city bounding box → Haiku drafts one natural line, or skips silently on failure. On demand, users can ask for city conditions or point-to-point drive times (get_travel_time, live traffic vs. free-flow). Landmark destinations (White House, Fenway, LAX) get resolved to street addresses by Sonnet before geocoding — TomTom's geocoder is a mapping API, not a search engine, and mis-ranks landmark names.
  • Price watches use SerpAPI Google Shopping (shopping.py): user texts "watch these sneakers under $80" → add_price_watch tool saves to price_watchesrun_price_watches job runs every 6h. Each tick: SerpAPI Google Shopping query on the product name, Haiku picks the cheapest genuine match from the top candidates (guards against firing on unrelated accessories/refurbs), first successful check establishes a baseline silently, subsequent checks alert when the target is hit or the price drops ≥ 15% from baseline. Cooldown defaults to 12h. Fails silently on any SerpAPI error — never surfaces "shopping tool failed" to the user.
  • Proactive outbound is scheduled: mornings at each user's local time (5-min tick, catch-up window, per-day guard), breaking-news alerts every 60 min (score ≥ 8, 1–9pm local send window), follow-ups every 4h (Haiku picks one ongoing thread, Sonnet drafts, 1–7pm window, 3-day gap guard), and missing-data asks every 60 min for users onboarded without a city so mornings can target them (7-day cooldown, US-daytime UTC window; DATA_ASK_DRY_RUN=1 to preview).
  • Reminder delivery uses FOR UPDATE SKIP LOCKED on Postgres — safe for multiple scheduler ticks, no double-sends.
  • Twilio webhook signatures (HMAC-SHA1) validated on every inbound request. All DB queries parameterized and scoped to phone number.
  • Tool routing is hard: get_weather → NWS/Open-Meteo only, get_price → CoinGecko/yfinance only, traffic tools → TomTom only, product price watches → SerpAPI only, web search → Tavily news mode. No overlap, no hallucinated data.