Inspiration

Every group chat has the same graveyard of messages: "we should hang out soon!" Everyone means it, and nobody plans it. Planning means finding a time that works, picking something everyone can afford and get to, and chasing people for replies. Saying "no" in front of the whole group is awkward, so people go quiet instead.

And if you're new to a city, or just moved for school, you don't even have a group chat to start from.

We wanted to turn a moment of wanting company into a real plan, and then into a routine, without asking anyone to download another app.

## What it does

What's Cooking is a text-first assistant that runs over iMessage. It has two modes.

### Find something to go to

  • Text the number "hi" and answer three quick questions: what you're into, which neighborhoods you're around, and when you're free with what budget. A fourth asks about access needs (step-free, sensory-friendly, kid-friendly), which are a hard filter. We never send something that doesn't meet them.
  • Every Sunday it texts a few ranked events near you. Reply 1 to join, INFO 2 for details, or text a mood like "something outdoorsy".
  • Anyone can host: HOST taco potluck sat 6pm cal anderson puts it on the map and into neighbors' weekly texts. Guests reply BRING <dish> for a shared "who's bringing what" list, and hosts get a text when someone joins.

### Plan with friends

  1. Ask. Text what you feel like: "free Sunday 2 to 6, something active, under $15."
  2. Private replies. The assistant messages each friend one-on-one. Their availability, budget, and travel limits stay private, and the organizer can't read them.
  3. Match. A deterministic engine checks every activity and start time against everyone's hard constraints, then ranks the plans that actually work.
  4. Confirm. Everyone accepts the exact plan (time, place, cost) by texting CONFIRM 1. Silence or "maybe" never counts as yes. As replies arrive, the others see ✅ Arwaad is in (2/4) or ❌ Arwaad passed, so nobody is left guessing.
  5. Coordinate. Once confirmed, the assistant relays messages between the confirmed people only. LATE 10 sends a minimal heads-up, and LEAVE is immediate.
  6. Change plans over text. Anyone can text CHANGE 3-6pm. The model reads the time, the assistant creates a new plan version, and asks everyone to reply CONFIRM 2. The current plan stays in force until all of them accept. A late CONFIRM 1 can't confirm the new version, and once everyone accepts, the updated plan goes out and the reminder moves to match.
  7. Run it back. After the outing, each person privately says whether they want to go again next week. Only the people who say yes start the next round, and they share fresh availability.

## How we built it

  • TypeScript domain core. The matcher, the outing lifecycle state machine, and a public "plan projection" that is the only data a group message or the language model ever sees.
  • The AI interprets; the server decides. The language model parses requests and explains options. It never decides whether a plan is feasible, never picks who gets messaged, and never confirms a plan.
  • Feasibility. A candidate plan passes only if, for every attendee $i$:

$$ \text{cost} \le \text{budget}i, \quad t{\text{start}} - \text{travel}i \ge \text{avail}^{\text{start}}_i, \quad t{\text{end}} + \text{return}_i \le \text{avail}^{\text{end}}_i $$

plus required needs, venue hours, and the activity's min/max headcount. Unknown facts are flagged as unresolved, never

assumed to pass.

  • Ranking. Feasible plans are ordered lexicographically: more willing attendees, then more explicit "yes" votes, then lowest worst-case travel (so no one friend always gets the long trip), then total travel, then cost.
  • Photon iMessage layer. The bot runs live on a Photon 2.0 iMessage line through the Spectrum SDK. Inbound texts become commands (CONFIRM, PASS, LATE, LEAVE, STOP, CHANGE). Replies are recorded against the exact plan version, and every provider message ID is deduplicated, so a repeated delivery can't confirm twice. Outgoing messages go through an outbox with idempotency keys and retries, and STOP cancels queued sends. Consent is rechecked at send time.
  • Structured model output. A free OpenRouter model (Nemotron) reads requests under a JSON schema, and the result is validated with zod. If the output is invalid we retry, then fall back to a second free model and then Gemini. Anything unstated comes back as null instead of a guess, and the server converts local times to UTC, never the model.
  • Tests. 49 automated checks cover consent and STOP, private-data leaks, stale versions, everyone-vs-flexible group modes, change approval, cancellation, repeat opt-ins, duplicate messages, and the relay.
  • Demo. A clickable iPhone-style web app that runs the same ranking engine in the browser on labeled sample events and a sample friend circle. It stays in sync with the live iMessage bot.

## Challenges we ran into

  • Privacy vs. explainability. We wanted to explain why a plan was chosen without revealing that Kai's budget ruled out the restaurant. We solved it with an allow-list projection: explanations can only use shared plan fields.
  • "Yes" is ambiguous. "Yes, I like pickleball" is not "yes, I'll be there at 2:30." We separated interest, willingness, and confirmation of an exact plan version.
  • Changes after confirming. A later start time shouldn't silently replace a plan people already agreed to. Versioned proposals keep the current plan valid until everyone approves the new one.
  • No native group chats on our iMessage line. Photon's shared line can't create group chats, and a dedicated line wasn't available to us. We built a relay instead: the bot forwards messages between the confirmed attendees only, labeled in every message so no one thinks it's a native thread.
  • Chat vs. commands. Once a plan is confirmed, ordinary messages are chat, so "can we start at 3?" was getting forwarded as chat and never changed the plan. We made changes an explicit CHANGE command.
  • Model flakiness. We hit demand spikes and a retired model name along the way. The fallback chain and strict validation mean a bad model response never changes anything.
    • Not having support for iMessage groupchats

## What we learned

  • The hard part of social planning isn't finding ideas. It's consent, timing, and follow-through.
  • Letting an LLM propose while deterministic code decides makes the system both flexible and trustworthy.
  • Announcing who confirmed and who passed on a specific plan makes the group move faster, while availability, budget, and travel limits stay private. We made that tradeoff deliberately.

## What's next

  • Native iMessage group chats with only the confirmed attendees, on a dedicated Photon line
  • Optional calendar free/busy connection (never treating an empty calendar as a "yes")
  • Live venue and event data
  • Checking requested changes against everyone's availability and venue hours
  • Meeting new people safely: mutual acceptance, public venues, block and report

Built With

Share this project:

Updates

Submission history