ORCA: the agent pod that gets you to the tournament
ORCA finds an athlete's next tournament, books the flight and hotel around it, and never touches a booking without a human saying yes first. A text comes in, a recommendation and a confirmed booking go out. The whole thing runs as a single text thread, because that's actually where athletes are, not a dashboard they have to remember to open.
Named for the orca: an apex predator that hunts in coordinated pods, each member with a job, none of them freelancing. That's the architecture. One orchestrator (doesn't it sound like orca?), three specialist agents, no agent ever acts alone.
Track: Collaborative Partner· Repo: https://github.com/HarshithaS2023/agentic_hackathon · Full detail in the technical summary.
The one-minute version
- Live Google Search grounding, never scraped. USTA and TennisLink ban scraping in their terms. ORCA never touches their pages directly. Every tournament discovery runs through Gemini's own Search grounding tool instead, and only when a human is actively watching the dashboard, because that's what Google's grounding terms actually require.
- Nothing books without a YES. The confirmation state machine isn't a prompt asking nicely. The booking tools are structurally unreachable until a prior confirmed state exists in session memory.
- One orchestrator, zero rogue agents. The three specialist agents are mounted as tools, not conversational hand-offs, so control never leaves the orchestrator mid-flow.
- Recommendations that actually read the player. Two early-round losses in a row and ORCA stops recommending tough draws and starts recommending confidence-building matches instead.
- SMS-first, dashboard-second. The dashboard exists for the one thing SMS legally can't do: show a live grounded search to a present human. Everything else routes through text.
Inspiration
Athletes who manage their own logistics, juniors, transitioning pros, anyone without a support staff, spend nearly as much time on travel and entry logistics as they do training. Finding the right level tournament. Booking a flight that actually lands before check-in. Remembering to prep two days out instead of scrambling the morning of. None of that needs a human doing it by hand, and none of it should happen without the athlete explicitly saying go.
What it does
Text ORCA "find me a tournament near San Diego" and it:
- pulls the player's ranking and recent match history, applies a form check (2+ early-round losses in the last 3 matches drops the recommendation to a lower level or straight to practice matches),
- once a tournament's picked, derives the trip's travel window from the event schedule itself and finds flights and hotels around it, enforcing a real arrival buffer so nothing lands after check-in closes,
- stages the booking and waits. Nothing gets written until the player texts YES, with a 30-minute window before the offer expires,
- once confirmed, writes the booking, fires the calendar and receipt-email effects, and two days out, sends a prep reminder on its own, no chat required.
A player opens the dashboard Monday morning and clicks search. ORCA runs a live grounded Google Search, shows exactly what it searched, structures the results, and writes them into the shared tournament store. That player then texts "what should I play next," and the orchestrator reads that same store, ranks it against her match history, and recommends a Futures event two levels below her ranking because her last three matches were first-round losses. She texts back asking about travel. ORCA reads the tournament's dates, builds a trip window, and comes back with flight options that all land the night before. She confirms. A booking record gets written, a calendar stub fires, and two days before the event she gets a text telling her to get her recovery in.
How we built it
- Google ADK orchestrates the system. The orchestrator mounts tournament, hotel, and flight agents as tools, not ADK sub_agents, specifically so control never transfers away mid-conversation. The confirmation state machine has to be the one thing that's always in charge.
- Gemini via Vertex AI powers every agent, ADC auth only, no API keys, per org policy.
- Tournament discovery runs as a two-step pipeline, not one call. Gemini won't combine Search grounding with function-calling tools in a single request, so step one only searches, step two only structures the output. Persistence happens in a callback after both steps finish, plain Python, not a tool call, which is what keeps the search step from ever touching a database write.
- The search agent is gated to the dashboard on purpose. Google's grounding terms require showing the person who triggered a search what was searched. SMS has no surface for that, so the search-grounded agent is simply unreachable from the SMS code path. Not a runtime check, a structural fact.
- Flask bridges the React dashboard to the agents through a small
/api/dashboard/*surface, with agent calls marshaled onto a persistent background event loop so a slow LLM call never blocks the rest of the UI. - A local JSON store, shaped exactly like the Firestore collections we're building toward, stands in for the database. Every function is a body-only swap away from a real
firestore.Client()call.
Challenges we ran into
- Scraping was never on the table. USTA and TennisLink both prohibit it outright in their terms, and their real developer partnership programs weren't something we could get into on a hackathon timeline. We had to find a legitimate path to real tournament data or fake the whole thing thus we opted for a live search feature.
- Gemini refuses to mix Search grounding with function tools in one request. Splitting search and extraction into two agents, chained through a SequentialAgent, was the fix.
- Keeping the confirmation window meaningful. A pending action that never expires is a liability. A 30-minute TTL on
pending_action, checked before any booking tool runs, closes that gap. - Session state living only in memory is a real limitation we're not hiding from. It doesn't survive a restart and won't work past one instance. Flagged directly in the code, not glossed over.
Accomplishments we're proud of
- A booking flow where "the agent won't book without confirmation" is a structural guarantee, not a prompt instruction we're hoping the model follows.
- A tournament discovery pipeline that respects real ToS constraints instead of quietly scraping around them.
- A recommendation agent that reads actual form, not just ranking, and adjusts what it suggests accordingly.
- A channel gate, SMS versus dashboard, that's enforced by what code is reachable from where, not by a flag that could be forgotten.
What we learned
The hard part of an agentic system isn't getting the model to do the right thing most of the time. It's making the wrong thing structurally impossible: unreachable tools, expiring state, a search agent that literally cannot be called from the one place it wouldn't be compliant.
What's next
- Twilio integration, to make the SMS side real instead of a stubbed channel.
- Swap the local JSON store for Firestore. The schema's already shaped for it.
- Wire the calendar and email stubs to real Google Calendar and SendGrid/SMTP calls.
- Persistent session storage, so a pending confirmation survives a restart instead of living only in process memory.
What's real and what's mocked
The agent reasoning, the structured recommendations, the confirmation state machine, and the live Google Search grounding pipeline are all real and running, not simulated for the demo. What's mocked: Firestore (a local JSON file shaped identically), authentication (a hardcoded demo player id), flight and hotel inventory (deterministic mock data), and Google Calendar, email receipts, and Twilio SMS (all log-line stubs behind the same function signatures the real integrations will use). Nothing here is a placeholder pretending to be a feature. It's a real system with clearly marked seams for where the real integrations plug in.
Built With
- flask
- gemini-(via-vertex-ai)
- google-adk
- google-search-grounding
- javascript
- pydantic
- python
- python-dotenv
- react
- vite

Log in or sign up for Devpost to join the conversation.