Project Info
Inspiration
Every year during college move-out season, the curbs fill up with piles of mattresses, desks, chairs, and electronics – a lot of it perfectly donatable or recyclable – that fester in the outdoors, sometimes for weeks. As rising college sophomores moving out on our own for the first time, we all experienced and saw friends experience the difficulty of responsibly disposing of bulky household items. There was just so much information scattered across websites, and it was a hassle to find a disposal pathway (donation, free bulky waste/mattress/e-waste collection services, junk haulers) that was available, cheap, and easy to utilize. The information gap was exacerbated by the fact that we needed to study for finals and pack before our leases ended. Of the 87 people that Joon surveyed this spring, 89% were unaware of existing waste disposal services, yet 58% of them said they would use one or more of these services had they been aware.
What it does
Getting rid of a mattress, a printer, or a half-used can of paint shouldn't take an afternoon of googling conflicting city pages. SnapCycle turns it into a single photo. After getting onboarded (entering your address and zip code), you snap the item you want to dispose of, and the app identifies it, finds every legitimate local way to get rid of it, and lays them out as ranked, tappable cards: donation, free city bulky-waste pickup, recycling collectives, household-hazardous-waste and e-waste drop-offs, and paid junk haulers. Each card carries the factors that drive the decision: cost, environmental friendliness, whether there's doorfront pickup, and how far you'd have to drive if there isn't. A filter lets you sort by whatever matters most to you, and the cards reorder instantly. If the photo is unclear (two items in frame), the agent asks a quick clarifying question instead of guessing. And if nothing's available nearby, it tells you and offers to widen the search rather than dead-ending. Tapping a card is where it goes from information cards to done-for-you. The app spawns an action agent that schedules the disposal for you: it auto-fills the donation or service web form, displays a tap-to-call number for city and collective scheduling, or, for junk haulers messages the three closest, highest-rated haulers with a photo of your item.
How we built it
The system runs in three stages. A knowledge layer uses a Browserbase browsing agent to crawl local recycling ordinances, municipal bulky-waste pickup programs, donation centers, free mattress/household hazardous waste/e-waste collectives, and hauler listings, then structures and embeds them into a RAG vector store keyed on item category and location, making the app's suggestions specific to where the user actually lives instead of generic advice. A scan is handled by a perception-and-retrieval agent running Gemini 2.5 Flash with its state managed in Redis. The agent identifies the item, runs a disambiguation loop when confidence is low, then queries the RAG store with the item plus location to assemble and rank the cards – including a no-results fallback that re-queries on a broadened scope when nothing turns up locally. When the user picks a card, our Simular action agent executes the chosen pathway, branching on how that option can be scheduled: web-form automation for donation and service sites, a tap-to-call number for city and collective scheduling, and the Twilio hauler-bidding flow, where it sends the item photo to the top three haulers (based on closeness and rating) on Twilio and renders their quotes as they stream in, continuously re-sorted low to high so the user can proceed with anyone before all three reply. The frontend is a mobile-first interface built around the card grid, the priority filter, and the live-updating quote list. Arize was used to monitor the agent's outputs. We used the data to optimize the prompt output and the input from agent to agent to create a system that minimizes token usage while increasing agent output accuracy. Sentry was used to check if the app's code was functioning properly, mainly node-related frontend issues that would interfere with overall workflows that could not be found using traditional smoke tests. Claude and Claude Code were used to plan and generate the agentic workflow flowchart.
Challenges we ran into
IP address for frontend and backend kept changing, making it hard to send request and debugging. Accessing Yelp via a virtual browser on the app with Playwright was an issue Integrating 5 sponsor API's throughout the stack
Accomplishments we're proud of
There are plenty of information aggregators online, but to our knowledge, we are the first to take a step further by helping users actually follow through with the disposal option that best suits their needs and preferences. End-to-end flow from camera → AI identify → decision → live disposal pathway discovery → saved item in one app. Service discovery that is adaptive to locality and the specific item in question Dividing the labor effectively to leverage each of our strengths and experiences
What we learned
A software that dumps aggregated information to students who are not is not enough. It must guide them
What's next
Add an onboarding screen that takes in the user's address and zip code, and subsequently, runs the browserbase agent once (after this initial run, a browserbase agent doesn't run again) – right now, browserbase curates a RAG database for each image uploaded Incorporate Redis memory to identify trends in users' preferred method of disposal (e.g. someone who prefers donation over junk hauling) Add a voice agent that the user can converse with in multiple languages instead of typing things out
Snapcycle
A mobile app that helps you responsibly get rid of large, awkward, or non-traditional items. Photograph an item, let AI identify it, and the app figures out the simplest correct path: throw it in the right home bin, or — if it needs special handling — research real local disposal pathways (donation, city bulky pickup, junk haulers, mattress/e-waste/HHW collectives, reuse events) grounded in live web search. It can fill in a hauler's booking form for you while you watch, or text every local junk hauler at once and stream their price quotes back in real time. A profile tracks how many items you've donated, sold, and kept out of the landfill.
Built with Expo / React Native + Supabase, and a Python / FastAPI backend that orchestrates a multi-agent pipeline over Google Gemini, Browserbase, Redis (cache + vector RAG), and Twilio (hauler SMS bids) — with full tracing via Arize Phoenix and silent-failure capture via Sentry.
How it works (end-to-end flow)
┌──────────────────────── Expo app (src/) ────────────────────────┐
Onboarding ──────▶│ permissions → address/zip → "build my local guide" │
(once per loc.) └──────────────────────────────┬──────────────────────────────────┘
│ POST /api/research
▼
Browserbase search × N + Gemini ──▶ persistent location
(curbside, bulky, HHW/e-waste, RAG in Redis
donation, mattress, reuse…) (location_rag:{zip})
Scan item ─▶ /api/identify (Gemini vision) ─▶ Confirm (+ optional "it's broken" note)
│
▼
/api/triage ──┬─ disposableAtHome → "put it in the {trash|recycling}" (home-disposal)
(RAG-grounded)│
└─ needs special pathway
│
▼
/api/disposal-options ── Browserbase (category-aware search) + RAG + Gemini ──▶ ranked
DisposalCards (donation / city_bulky_pickup / junk_haulers /
recycling_collective / hhw / ewaste), Redis-cached
│
▼ user taps a card
/api/card-detail ── two cooperating Redis agents:
Agent 1 research_card_detail (next steps + eligibility constraints, RAG + fetch)
Agent 2 decide_action (summary + recommendation, routes the UI mode)
│
┌───────────────┼───────────────────┬───────────────────────────────┐
▼ ▼ ▼ ▼
mode "summary" mode "form" mode "phone" junk-haulers path
(read + link) Agent S fills the show number to call ┌─────────────────────┐
booking form in a │ /api/haulers/bids │
live Browserbase │ Browserbase finds │
browser (review + │ local haulers w/ │
submit yourself) │ phones → Twilio │
│ texts them ALL → │
│ replies stream back │
│ as price quotes │
└─────────────────────┘
│
▼
/api/chat ── ask follow-up questions, grounded in the location RAG + on-screen cards
│
▼
confirm-disposal ──▶ recorded to Supabase (history + profile stats)
The donate / sell / discard framing, profile stats, and history all live in Supabase; everything AI- or web-grounded runs on the backend so the Gemini / Browserbase / Twilio keys never ship in the app.
Architecture
┌─────────────────────────┐ ┌──────────────────────────────────────────┐
│ Expo mobile app (src/) │ │ Python FastAPI backend (backend/) │
│ │ │ │
│ • Supabase auth │──JWT───▶│ deps/auth verify Supabase JWT │
│ • Expo Router flow │ │ rrr_identify Gemini vision │
│ • disposal-context │ │ rrr_location_research Browserbase+Gemini │
│ • onboarding-context │ │ rrr_triage home-bin vs special │
│ • WebView live views │ │ rrr_disposal cards (Browserbase+RAG) │
│ • bid quote polling │ │ rrr_card_agent 2 Redis agents │
│ │ │ rrr_chat RAG-grounded chat │
│ │ │ agent_s Agent S form-fill │
│ │ │ rrr_haulers Browserbase hauler disc. │
│ │ │ twilio_bids/_sms SMS bid blast + webhook │
└─────────────────────────┘ └───────────────┬────────────────────────────┘
│ │
▼ ┌──────────┬───────┼──────────┬──────────────┐
Supabase (Postgres+Storage) ▼ ▼ ▼ ▼ ▼
Gemini API Browserbase Redis Stack Twilio (SMS)
(vision + (web search (cache-aside + blast haulers,
text + + fetch + vector RAG + collect quote
embeddings) live sess.) location RAG) replies via webhook
Observability: Arize Phoenix (OpenInference tracing of every agent/LLM stage)
Sentry (captures silent failures hidden behind graceful fallbacks)
- Gemini does vision identification, all text synthesis, and 3072-dim embeddings.
- Browserbase powers live web search + page fetch (so disposal options and haulers are real, not hallucinated) and full cloud browser sessions that Agent S drives via Playwright over CDP, streamed to the phone in a WebView.
- Redis Stack is three things: a cache-aside layer (TTL'd), a vector index for semantic item-query matching, and the persistent location RAG (no TTL) built once during onboarding and shared by triage, cards, card-detail, and chat. It's also the source of truth for live bid sessions (so the SMS webhook and the polling GET agree).
- Twilio runs the junk-hauler bids: after Browserbase discovers local haulers with phone numbers, the backend texts them all a templated quote request in parallel; each reply hits an inbound webhook, gets parsed for a price, and streams into the UI.
- Agent S (
agent_s.py) fills a pickup/booking form in a live Browserbase browser. It is intentionally conservative — it never submits; the user reviews and submits in the live view. - Graceful degradation everywhere: no Twilio number, Browserbase fetch misses, Gemini
JSON-parse failures, Redis/geoip errors all fall back cleanly — and each is reported to
Sentry via
capture_silent_failure()so smooth UX never hides a real failure. Seesentry.md.
Backend endpoints
| Endpoint | Agent / service | Purpose |
|---|---|---|
GET /health | — | Public health check |
POST /api/research | rrr_location_research | One-time onboarding research → persistent location RAG in Redis |
POST /api/identify | rrr_identify | Gemini vision item identification (structured JSON) |
POST /api/triage | rrr_triage | First pass: home trash/recycling vs. a special pathway (RAG-grounded) |
POST /api/disposal-options | rrr_disposal | Browserbase + RAG + Gemini → ranked DisposalCards (Redis-cached) |
POST /api/card-detail | rrr_card_agent | Two Redis agents: research the pathway, then recommend an action/mode |
POST /api/chat | rrr_chat | Follow-up Q&A grounded in the location RAG + on-screen cards |
POST /api/agent/form | agent_s | Start Agent S filling a booking form in a live Browserbase session |
GET /api/agent/form/{id} | agent_s | Poll Agent S form-fill session status |
POST /api/haulers | rrr_haulers | Browserbase + Gemini hauler discovery (real businesses w/ phones) |
POST /api/haulers/bids | twilio_bids | Discover haulers + text them all a quote request (returns a bid session) |
GET /api/haulers/bids/{id} | twilio_bids | Poll the bid session — quotes stream in as haulers reply |
POST /api/haulers/sms-webhook | twilio_bids | Twilio inbound webhook — a hauler's reply becomes a quote |
POST /api/schedule | rrr_schedule | Gemini scheduling-confirmation copy |
POST /api/services | rrr_service_discovery | Browserbase + Gemini donate/sell/discard service discovery |
GET/POST /api/location/* | geoip / location | IP location detect + municipal rule sets |
POST /api/recycle, /api/recycle/upload | agent/orchestrator | Legacy recycling-instruction pipeline (text/image) |
GET /debug/sentry-test | — | Emits a test Sentry event (remove/protect in prod) |
/api/* routes verify a Supabase JWT when AUTH_REQUIRED=true; in dev
(AUTH_REQUIRED=false) they allow anonymous access and attach the user id when a token
is present. The Twilio webhook is unauthenticated (called by Twilio) and optionally
verifies the X-Twilio-Signature header.
Prerequisites
- Node.js 20+ (mobile app) and Python 3.11+ (backend)
- Docker for Redis Stack (the Search module powers the vector index + RAG)
- A Supabase project
- A Google Gemini API key — https://aistudio.google.com/apikey
- A Browserbase API key + matching project ID — https://browserbase.com (the project ID must belong to the same account/project that issued the key)
- (optional) a Twilio account (SMS hauler bids), a Sentry DSN, and an Arize Phoenix endpoint + key
- The app runs in Expo Go for quick testing; live Browserbase views render in a
react-native-webview.
1. Supabase setup
- Create a project at https://supabase.com.
- Authentication → Providers → Email: enable email/password.
- SQL editor → run
supabase/schema.sql(createsprofilesanditems, the leaderboard view, theitem-photosbucket, stats triggers, and RLS on everything). - Project Settings → API → copy the Project URL and anon/publishable key.
- (optional) add a test user with Auto Confirm User for an easy dev login.
2. Mobile app
npm install
cp .env.example .env
Fill in .env:
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-or-publishable-key
EXPO_PUBLIC_API_URL=http://localhost:8000
# Optional
EXPO_PUBLIC_SENTRY_DSN= # same DSN works for app + backend
# Dev-only: prefill the login form so you don't retype an account each reload
# EXPO_PUBLIC_DEV_EMAIL=admin@rrr.test
# EXPO_PUBLIC_DEV_PASSWORD=your-dev-password
npx expo start --clear
Scan the QR with Expo Go (phone + computer on the same Wi-Fi).
.envis gitignored.EXPO_PUBLIC_*values are embedded in the app bundle — fine for the Supabase anon key (protected by RLS). Gemini / Browserbase / Twilio keys are never in the app; they live only on the backend. On a physical device setEXPO_PUBLIC_API_URLto your machine's LAN IP (e.g.http://192.168.x.x:8000), notlocalhost.
3. Backend
cd backend
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
pip install -r requirements.txt
cp app/.env.example app/.env
Fill in backend/app/.env (only the keys matter; the rest have sane defaults):
GOOGLE_API_KEY=your-gemini-key
GEMINI_MODEL=gemini-2.5-flash
BROWSERBASE_API_KEY=your-browserbase-key
BROWSERBASE_PROJECT_ID=your-browserbase-project-id # must match the key's account
REDIS_ENABLED=true
REDIS_URL=redis://localhost:6379/0
# Twilio — optional; enables the hauler SMS "bids" flow. Without it, the app still
# discovers haulers and shows tap-to-call numbers (no auto-texting).
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
TWILIO_FROM_NUMBER= # E.164 SMS-capable number, e.g. +14155551234
TWILIO_VALIDATE_SIGNATURE=false # verify X-Twilio-Signature on the inbound webhook
# Optional Supabase JWT verification (leave false for dev)
SUPABASE_URL=
SUPABASE_ANON_KEY=
AUTH_REQUIRED=false
# Observability (optional)
SENTRY_DSN= # captures silent failures; blank = disabled
PHOENIX_COLLECTOR_ENDPOINT= # Arize Phoenix tracing; blank = disabled
PHOENIX_API_KEY=
Agent S form automation needs Playwright's Chromium once:
playwright install chromium
Start Redis Stack (provides the Search module the vector index + RAG need):
docker run -d --name recycle-redis -p 6379:6379 redis/redis-stack-server:latest
# after a reboot: docker start recycle-redis
Redis holds cache + vector index + the persistent location RAG + live bid sessions. The RAG (built once per zip during onboarding) has no TTL so it survives restarts; everything else is TTL'd. Skip cleanly with
REDIS_ENABLED=false— the backend still boots and degrades.
Run the backend (bind all interfaces so a physical device can reach it):
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
Twilio webhook: for haulers to reply, Twilio must reach
POST /api/haulers/sms-webhook. In local dev, expose the backend with a tunnel (e.g.ngrok http 8000) and set that URL as the Messaging webhook on your Twilio number.
Verify: http://localhost:8000/health → {"ok":true,"status":"ok"}. From the phone's
browser, http://<your-LAN-IP>:8000/health should match (allow Python through the
firewall if it times out).
Project layout
src/
app/ Expo Router screens
(auth)/ login, signup
(tabs)/ index (home), history, profile
onboarding.tsx permissions → address → build local RAG
camera.tsx full-screen capture
flow/ processing → confirm → triage →
home-disposal | (location) → results → action →
agent-form | confirm-disposal · chat
item/[id].tsx item detail
components/ ui, flow, disposal, chat, camera, item, leaderboard, effects
contexts/ auth-context, onboarding-context, disposal-context
hooks/ use-auth, use-profile, use-items, use-camera,
use-theme, use-follow-through
services/ supabase, auth, api, onboarding, items, storage
constants/theme.ts warm flat design system (light-mode only)
types/, utils/
backend/
app/
main.py FastAPI app, Sentry init, Phoenix tracing, router wiring
config.py settings (reads app/.env and backend/.env)
observability.py Phoenix spans + Sentry capture_silent_failure()
api/ rrr (agentic), location, recycle routers
deps/auth.py Supabase JWT verification
schemas/rrr.py request/response models
services/
rrr_identify.py Gemini vision identification
rrr_location_research.py onboarding research → persistent location RAG
rrr_triage.py home-bin vs special-pathway triage
rrr_disposal.py ranked DisposalCards (Browserbase + RAG + Gemini)
rrr_card_agent.py two cooperating Redis agents (detail + recommendation)
rrr_chat.py RAG-grounded follow-up chat
agent_s.py Agent S form-fill over a live Browserbase session
rrr_haulers.py Browserbase + Gemini hauler discovery (real phones)
twilio_bids.py multi-hauler SMS bid orchestration + inbound reply handling
twilio_sms.py thin Twilio SMS wrapper (graceful when unconfigured)
rrr_schedule.py scheduling-confirmation copy
browserbase.py search / fetch / live sessions
gemini.py, embeddings.py, vision.py
cache.py, item_index.py (Redis cache + vector index), geoip.py
agent/, rules_cache.py, ... (legacy recycle orchestrator)
data/ bundled municipal rule sets (berkeley, stanford, ucla, ann_arbor)
evals/ Phoenix eval harness + sentry_probe.py
requirements.txt
supabase/schema.sql DB schema + RLS policies
sentry.md observability writeup: what Sentry catches that the app hides
Security & resilience
- Gemini / Browserbase / Twilio keys are backend-only and never reach the app.
- When
AUTH_REQUIRED=true, the backend verifies the Supabase JWT before doing any work. The Twilio inbound webhook is called by Twilio (no app JWT) and can verify theX-Twilio-Signatureheader (TWILIO_VALIDATE_SIGNATURE=true). - All Supabase tables have Row Level Security; the anon key can only touch the signed-in user's rows. The leaderboard view exposes aggregate counts + a non-PII handle.
- Agent S never auto-submits — it pre-fills the form in a live browser the user watches; the user reviews and submits. Hauler bids are sent to businesses' public quote lines, not personal numbers.
- Every external dependency degrades gracefully and reports the masked failure to
Sentry (see
sentry.md). Phoenix traces every agent and LLM call. .envfiles are gitignored in both the app (.env) and backend (backend/app/.env).
Analysis
View
Metric
- 6
- 5
- 1
- 1
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- CSSIn code
- FastAPIIn code
- JavaScriptIn code
- PythonIn code
- ReactIn code
- RedisIn code
- SQLIn code
- SupabaseIn code
- TypeScriptIn code
- Node.jsClaimed
9 of 10 appear in the indexed code. 1 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
- Claude CodeConfig · Commits
- CodexConfig
- CursorCommits
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
468 KB
Source files
141
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
prithsk/SnapCycle
192 files · 3.5 MB · @ af2b057
Structure
Interface
90 files · 47%Screens, components and styles rendered to the user.
API & routing
4 files · 2%Request entry points: routes, handlers and controllers.
Application logic
51 files · 27%Domain rules, services and shared utilities.
+5 moreData & schema
2 files · 1%Schema definitions, migrations and data access.
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- Python45%
- TypeScript43%
- Markdown9%
- SQL1%
- JavaScript1%
- CSS0%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
package.json
npm · 43- @expo-google-fonts/fraunces
- @expo-google-fonts/hanken-grotesk
- @expo/ui
- @expo/vector-icons
- @react-native-async-storage/async-storage
- @react-navigation/native
- @sentry/react-native
- @shopify/react-native-skia
- @supabase/supabase-js
- expo
- expo-camera
- expo-constants
- expo-device
- expo-file-system
- expo-font
- expo-glass-effect
- expo-haptics
- expo-image
- +25 more
backend/requirements.txt
pypi · 19- arize-phoenix
- arize-phoenix-client
- arize-phoenix-evals
- arize-phoenix-otel
- browserbase
- fastapi
- google-genai
- google-generativeai
- gui-agents
- httpx
- pandas
- playwright
- pydantic-settings
- python-dotenv
- python-multipart
- redis
- sentry-sdk[fastapi]
- twilio
- +1 more
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.