Inspiration
Our team members had gym memberships that were either about to run out or already lapsed going into this hackathon. And what we actually got from our gyms was one of those generic automated texts asking us to come back. Didn't work on any of us. If anything it was kind of annoying.
After some research, we found that churn in gyms runs something like 30-50% a year, and most of it happens in someone's first 90 days. Every existing tool out there (Keepme, Glofox) sure, they can tell you who's about to leave, but none of them stop and ask whether reaching out is even the right move. One study tracked 78,000+ gym visits and found that a real conversation with a staff member increases the likelihood of that member coming back the following month by about 20%.
What it does
An outbound voice-agent system that calls the gym members worth calling back, and is built to never call the ones a call would cost the gym money. A rule-based router reads each member's contract, attendance and call history and assigns one of four call types (renewal, reengagement, win back, or a narrow cancellation-save call) or excludes them, with a reason for every decision, including the exclusions. An auto-renewing member is never called, at any point in their term, however long they've been gone. Each call type runs on its own narrow ElevenLabs conversational agent, so a call never drifts into the wrong script. Config, what a gym can offer, its hours, its incentives, lives per-gym, not per-prompt, so the same three agents serve a gym with five incentives and a gym with nothing to offer. Every call's outcome and stated reason for absence feeds into the next call, so the agent never re-asks something already answered, and every result lands in a staff-facing dashboard in real time.
How we built it
Next.js and React on Vercel for the app; ElevenLabs Conversational AI for the four call agents, sharing prompt sections (Personality, Tone, Guardrails, Tools) assembled at sync time so a guardrail fix reaches every agent at once, Twilio for telephony, Supabase for call records, the only stateful table in the system. A Python pipeline generates the synthetic member dataset (Faker, a fixed seed, a frozen reference date) across five cohorts, openly disclosed as synthetic throughout. The conversation model is Gemini, chosen for latency over reasoning power since the prompts do no thinking of their own, they're a script with guardrails. Document extraction at gym onboarding runs on Claude Haiku, constrained to typed fields only, so an uploaded price list can never write a sentence a member hears. The router itself is deliberately rule-based rather than a churn model.A front-desk manager needs to be able to see why a member was called and disagree with it, and a call justified by a score nobody can read is a call a gym will stop trusting.
Challenges we ran into
Honestly, one of the harder parts wasn't even the AI: it was that we only had one Twilio account and one ElevenLabs account for the whole team. With four of us trying to build and test at the same time, we kept getting in each other's way: someone testing a call would tie up the same number someone else needed, or a prompt change one person pushed would break whatever someone else was in the middle of testing. A decent chunk of our actual coordination time went into working around that, not into the product itself.
On the technical side, the scariest bug we found wasn't in our logic. It was in the model. On about one call in eight, our first conversation model would start narrating its own thinking out loud mid-call, stuff like "the user gave a clear reason for their absence, as per step 5 of the Goal section," actually spoken to whoever was on the phone. We never would've caught that just reading the prompt, we only found it because we ran real test calls and listened to them. Ended up switching models over it. The other one that got us: our cancellation agent, meant to handle someone actively trying to quit, would offer a second incentive even after the member had already said no thanks to the first. That's the worst possible place for that bug to show up, since it's someone trying to leave getting pressured to stay. Small wording change in the prompt, completely different behavior once we found it.
Accomplishments that we're proud of
Honestly, the moment that got us most excited was a pretty simple one: the first time we actually had ElevenLabs hooked up, the system called one of us for real, and we could see it pull a real insight out afterward. That's when it stopped feeling like a hackathon exercise and started feeling like something real.
From there, we're proud we built an actual testing setup for this: 80 automated checks on the logic side, plus 31 simulated phone conversations run against our live agents, and it already caught both bugs above before either reached a real call. We're also proud we didn't fudge a number we didn't have: our router can explain exactly why it made every decision, but we don't have real gym data yet to say how often those decisions were right, so instead of inventing an accuracy percentage that sounded good, we cut it and said so. On the money side we did the same thing. Instead of guessing a conversion rate, we worked out the break-even: at about $70 a call, you only need one save in roughly 1,200 calls for the whole thing to pay for itself, and our queue for a gym this size sits at 173 calls, well under that.
What we learned
Building with AI is just a different kind of engineering than what we're used to, and that turned out to be the real focus of this project as much as the gym-retention idea itself. You can't unit test a phone conversation the way you'd unit test a function: the same prompt can behave differently call to call, so most of what we actually learned was about how you verify an AI system is doing what you think it's doing, not just how you write the prompt. Reading a prompt and testing it against a real call surface completely different problems: the reasoning-leak bug was invisible on paper and obvious the second we heard it out loud. And wording matters more than it should. "Offer again unless they say no" and "don't offer again unless they give you a reason to" sound almost the same but produce totally different behavior, and we only found that out by testing it. On the product side, we came away thinking most retention tools only do half the job (good at telling you who's at risk, bad at knowing who to leave alone), and that turned out to be the more interesting problem to actually implement well.
What's next for Retention Router
The obvious next step is getting this in front of a real gym with real members, because right now everything we've built is validated on synthetic data. We know the logic is sound, we don't yet know if the decisions are actually right in the real world, and we want to find that out rather than assume it. We'd also want to hook straight into the platforms gyms already use (Mindbody, Glofox, PushPress) instead of making someone upload a CSV, and add a proper do-not-call check before any real number gets dialled. There's also one scenario we haven't fully tested yet: a real member pushing back in a messier way than our scripted test member does, and we want an actual transcript of that before we trust it. Longer term, we want to turn the call data we're already collecting into something a gym owner can act on, like a real revenue-retained number, or grouping "why members are leaving" into actual themes so a gym can fix the underlying problem instead of just calling more people. And we'd like an independent model judging our test calls instead of the same company's model grading its own agent. That's the biggest gap in how we're measuring ourselves right now.
Built With
- anthropic
- claude
- elevenlabs
- faker
- gemini
- next.js
- postgresql
- python
- reac
- supabase
- tailwindcss
- twilio
- typescript
- vercel
Log in or sign up for Devpost to join the conversation.