WeaveHacks 4 | June 2026 | https://github.com/xinyuhwang/Twinder
DEMO: https://www.loom.com/share/37234a31986847f9a90c1a4fd0f812f6
Your AI digital twin networks on your behalf. Built from your “second brain” (interests, personality, experience), your twin chats with other twins in a virtual room — like a networking event that runs while you sleep. The best conversations surface as matches, ranked by “vibe score.” Then you meet IRL.
- Networking relies on serendipity; introverts miss connections
- Your twin can talk to everyone — you only meet the best matches
- Agents start the conversation; humans can drop in and continue it
- Works for professional networking, dating, community building
- FastAPI + uvicorn (async Python)
- SQLModel (SQLAlchemy + Pydantic)
- Redis (Streams, Pub/Sub, Hashes) — sponsor integration
- LiteLLM (provider-agnostic LLM calls: Claude, GPT-4o, etc.)
- Google OAuth + JWT auth + dev-login shortcut
- SQLite for persistence
- 7 seeded demo users with rich personas (auto-seeded on startup)
app/
├── main.py # FastAPI app, lifespan, CORS, auto-seeds demo users
├── config.py # Pydantic Settings (.env)
├── deps.py # Auth dependency (JWT)
├── database.py # SQLite engine
├── models.py # User, Room, RoomParticipant
├── schemas.py # Request/response models (incl. MatchCard, ArenaResponse)
├── redis_client.py # Async Redis pool (auto RESP2/3)
├── llm.py # LiteLLM wrapper (swap providers via env)
├── seed.py # 7 demo users: Alexis, Haley, Leo, Maya, Jordan, Priya, Marcus
├── auth/
│ ├── oauth.py # Authlib Google OAuth
│ └── router.py # /auth/google, /auth/callback, /auth/me, /auth/dev-login
├── users/
│ └── router.py # /users/me, /users/{id}
├── rooms/
│ ├── router.py # Room CRUD, matchmaking, takeover, completion
│ └── matchmaker.py # Redis list-based pairing
├── chat/
│ ├── manager.py # WebSocket ConnectionManager
│ └── router.py # WS /ws/rooms/{room_id}
├── arena/
│ └── router.py # Arena batch mode endpoints
└── agents/
├── prompts.py # Twin persona + match card scoring prompts
├── engine.py # Agent conversation loop (chatroom mode)
├── arena.py # Arena batch engine (speed-date all agents)
└── scorer.py # Post-conversation vibe scoring
| Method | Path | Description |
|---|---|---|
| GET | /auth/google | Start Google OAuth flow |
| GET | /auth/callback | OAuth callback -> JWT cookie + redirect |
| GET | /auth/me | Current user from JWT |
| POST | /auth/dev-login | Dev shortcut: create/find user, get JWT |
| POST | /auth/logout | Clear cookie |
POST /auth/dev-login?name=Alexis — returns JWT for seeded demo user.
POST /auth/dev-login?name=NewUser&persona=... — creates a new user.
| Method | Path | Description |
|---|---|---|
| GET | /users/me | Get profile |
| PUT | /users/me | Update name, persona |
| GET | /users/{id} | Public profile |
| Method | Path | Description |
|---|---|---|
| POST | /arena/start?mode=hackathon | Run agent against all others, ranked |
| GET | /arena/results | Fetch latest match cards |
| GET | /arena/conversation/{convo_id} | Eavesdrop on a specific arena convo |
POST /arena/start runs your agent in short (8-turn) parallel conversations
with all other users, then scores each and returns ranked match cards with:
score, headline, match_type, summary, overlaps, suggested_opener,
conversation_highlights, common_interests.
Modes: networking, hackathon, dating, custom.
| Method | Path | Description |
|---|---|---|
| POST | /rooms/matchmake | Join matchmaking queue |
| GET | /rooms/matchmake/status | Poll match status |
| GET | /rooms | List user’s rooms |
| GET | /rooms/{id} | Room details + vibe score |
| GET | /rooms/{id}/messages | Message history (Redis Stream) |
| POST | /rooms/{id}/takeover | Human replaces agent |
| POST | /rooms/{id}/complete | End convo, trigger vibe scoring |
| WS | /ws/rooms/{id}?token={jwt} | Real-time chat via Redis Pub/Sub |
| Method | Path | Description |
|---|---|---|
| GET | /health | SQLite + Redis health |
| GET | /docs | Swagger UI (interactive) |
| Key | Type | Purpose |
|---|---|---|
| room:{id}:messages | Stream | Message history (sender, role, content) |
| room:{id}:state | Hash | Turn state, msg count, human overrides |
| rooms:active | Set | Active room IDs |
| matchmaking:queue | List | Users waiting to be paired |
| matchmaking:result:{uid} | String | Match result (room_id), TTL 5min |
| room:{id}:events | PubSub | Fan-out to WebSockets |
| arena:{uid}:latest | String | Latest arena results JSON, TTL 1hr |
| arena-convo:{id} | Stream | Arena conversation messages, TTL 1hr |
- User enters arena
- Their agent has short conversations with all other agents (parallel)
- Each conversation scored into a rich match card
- Results returned ranked by score
- Frontend shows swipeable match cards with eavesdrop option
- Two users matched via queue
- Agents chat in real-time (20 turns, 3s pacing)
- Messages stream via WebSocket + Redis Pub/Sub
- Human can take over at any time
- On completion, vibe scoring via LLM
7 users with rich multi-paragraph personas:
| Name | Role |
|---|---|
| Alexis | AI engineer, climber, builder |
| Haley | Social/emotional product thinker |
| Leo | Backend/infra builder |
| Maya | Product designer, UX thinker |
| Jordan | Go-to-market, community |
| Priya | AI researcher, knowledge systems |
| Marcus | Indie hacker, storyteller |
Provider is swappable via LLM_MODEL in .env:
anthropic/claude-sonnet-4-20250514(Claude)gpt-4o(OpenAI)gpt-4o-mini(cheap testing)
Powered by LiteLLM — supports 100+ providers.
# 1. Get a token for a demo user
TOKEN=$(curl -s -X POST 'http://HOST:8000/auth/dev-login?name=Alexis' | jq -r .token)
# 2. Run arena (takes ~30-60s)
curl -X POST 'http://HOST:8000/arena/start?mode=hackathon' \
-H "Authorization: Bearer $TOKEN"
# 3. Get match cards
curl 'http://HOST:8000/arena/results' \
-H "Authorization: Bearer $TOKEN"
# 4. Eavesdrop on a conversation
curl 'http://HOST:8000/arena/conversation/{convo_id}' \
-H "Authorization: Bearer $TOKEN"- All endpoints except
/health,/docs,/auth/google,/auth/callbackrequire auth - Pass JWT as
Authorization: Bearer {token}header oraccess_tokencookie - For WebSocket: pass as query param
?token={jwt}
MatchCard: score, headline, match_type, summary, strongest_overlap, non_obvious_overlap, complementary_dynamic, suggested_opener, follow_up_questions, conversation_highlights, common_interests, opponent_id, opponent_nameArenaResponse: status, arena_id, match_cards[]MessageRead: id, sender_user_id, sender_name, role (agent/human), content, timestampUserRead: id, name, email, avatar_url, persona
Full schemas at /docs.
- Dev: local Redis (auto-falls back to RESP2 for older versions)
- Prod: Redis Cloud 8.x (us-east-1)
- Switch via
REDIS_URLin.env
# Start Redis (if using local)
redis-server --daemonize yes
# Install deps
uv venv && source .venv/bin/activate
uv pip install -e .
# Run (auto-seeds 7 demo users)
uvicorn app.main:app --reload
# Docs at http://localhost:8000/docs- Repo: https://github.com/xinyuhwang/Twinder
- Domain: twindr (setting up)
- Dev server: DO droplet (backend running)
- Redis is a hackathon sponsor — bonus points for usage
- Targeting: compelling live demo + clear narrative
- Demo flow: enter arena -> agents speed-date -> swipeable match cards -> eavesdrop -> meet