Inspiration
Agents write confident prose. The hard part is knowing the writing is true. A summarizing agent that quietly invents a prize amount or shifts a deadline by a week is worse than no agent at all, because you'll act on it and never know to check.
As a student, I kept a private list of time-boxed opportunities — hackathons, fellowships, grants — and still missed them, because they live on a dozen sites and only matter for a few weeks each. So I built the radar I wanted, around the failure mode I actually care about: an agent whose written output can be checked, not trusted.
What it does
Opportunity Radar is an async background agent that watches opportunity sources on a schedule. Each run it fetches sources (Devpost's live API today; the Source interface takes anything), normalizes and dedupes against persistent Firestore state so you only ever see what's new, scores each item against a transparent profile (vendor-sponsor bonus, USD prize floor, theme keywords, deadline urgency, field-size penalty), selects the top N, and then has Gemini 3.5 Flash write the brief from the selected items only.
The part that matters: a deterministic anti-invention gate validates every URL, dollar figure, and date in Gemini's brief against the selected-items input. If the model embellishes, the gate rejects the output, a deterministic renderer takes over, and the brief says so. The score breakdown prints with every run — no hidden weighting.
The architectural claim: the model is confined to narration, and the confinement is enforced in code and proven by executable tests — not asserted in a system prompt and hoped for.
How I built it
Deterministic Python owns everything factual: fetch, parse, dedupe, score, select. Gemini only narrates.
- Gemini 3.5 Flash via Vertex AI (
google-genai) writes the brief. - Google ADK provides the agent layer —
root_agentwith four custom function tools (scan_sources,get_new_since_last_run,score_items,write_brief). The agent chooses the order; the tools are deterministic. - Firestore holds cross-run state (
seen_ids,last_run) — the agent's memory between runs, which is what makes "only show me what's new" work across scheduled invocations. - A
Stateinterface keeps Firestore and a local JSON backend interchangeable, so the whole pipeline is testable offline with no cloud and no key.
39 pytest tests and an executable eval suite (exit 0/1, wired into CI) with negative controls: a planted fake URL, a fake dollar figure, and a fake deadline must each be caught.
Challenges I ran into
Prize strings are chaos — ₹ 100,093,499, $25,000 CAD, HTML currency spans. The parser refuses to guess: non-USD parses to None and the raw string is shown verbatim, because a wrong number in a brief is worse than no number.
A subtler bug bit late: Devpost writes same-month windows as "Aug 07 - 31, 2026". My parser split on " - " and tried to parse "31, 2026" — no month, so it silently returned None. Every same-month listing lost its deadline and its urgency score. Silent data loss inside the "deterministic" half is exactly the class of bug the gate can't catch, which was a useful reminder that grounding the model doesn't ground your own parser.
The hardest design problem was deciding what the model is not allowed to do. Letting an LLM do the dedupe and ranking would have been less code — but then no test can tell you whether last week's brief was right.
Accomplishments that I'm proud of
An agent whose written output is provably grounded — the gate is executable, not a claim. Run python evals/run_evals.py and watch it catch three planted lies. Most agent projects ask you to trust the prompt; this one hands you the test.
What I learned
Enabling billing silently moved my project off the Gemini API free tier into a prepay model, and the API started returning 429 RESOURCE_EXHAUSTED mid-build — the fix was switching transports to Vertex AI, which authenticates with application-default credentials instead of an API key. Also learned that Gemini 3.5 is served from Vertex's global location, not a regional one; us-central1 404s.
What's next for Opportunity Radar
More sources (fellowship and grant pages) behind the same Source interface, Cloud Run Jobs on a Cloud Scheduler cadence (written and documented, not yet executed), and per-item provenance so the gate can validate facts against the specific item they're attached to rather than the selected set as a whole.
Built With
- firestore
- gemini
- google-adk
- google-cloud
- python
- vertex-ai

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