Inspiration
Every CS student knows the ritual: you find a job posting, open a blank document, and spend three hours writing a cover letter that sounds like everyone else's cover letter. You tweak your resume bullets hoping the right keywords land. You submit and wait.
The problem isn't effort — students put in real effort. The problem is that they're doing it blind. They don't know which parts of their resume actually match the role, which gaps are disqualifying versus addressable, or how to frame a finance internship when they've only built Python ML projects.
We wanted to build something that closes that gap — not by helping students do the work faster, but by doing the diagnostic work for them and handing them finished, tailored materials.
What it does
First Move takes two inputs: a resume PDF and a target role. From there, the agent takes over.
It evaluates the resume quality first and branches based on what it finds. A strong resume goes straight to analysis. A thin resume — one with fewer than two relevant experiences or vague descriptions — triggers a clarification step where the agent pauses, asks targeted questions, and incorporates the answers before continuing. A domain-gap resume (strong fundamentals, missing specific domain knowledge like financial markets or systems programming) routes to a transferable-skills framing strategy.
From there it searches the live job description, scores fit across five competency dimensions, rewrites resume bullets to mirror JD language, and generates a personalized cover letter — all streamed live so the user watches the agent reason and work in real time.
The final output includes a match score, a radar chart, before/after bullet comparisons, a cover letter, and a cold outreach email template.
How we built it
The backend is a FastAPI app running on Google Cloud Run, streaming server-sent events to the frontend. Every agent step yields a JSON event as it completes, so the UI updates in real time rather than waiting for the full pipeline.
The agent uses Gemini 2.5 Flash for all reasoning — resume quality evaluation, fit analysis, bullet rewriting, cover letter generation, and five per-step reasoning narrations that stream into a live "Agent Reasoning" terminal panel. We use Gemini's ThinkingConfig to surface the model's actual reasoning tokens, not synthetic commentary.
The adaptive branching logic lives in agent.py as an async generator. The three strategy paths — strong, thin, domain gap — each produce different prompts, different cover letter framing, and different UI feedback. The agent's decision is visible to the user, not hidden in a black box.
The frontend is React with Tailwind CSS, deployed on Vercel. Application history is stored in Supabase with Row Level Security. Guests get localStorage persistence; signed-in users get cross-device sync. The comparison view overlays two radar charts and calls Gemini for a prose insight about patterns across applications.
Challenges we ran into
**Streaming large payloads. **The SSE stream worked fine for small step events but broke silently on the complete event, which carries a full cover letter, rewritten bullets, and email template — typically 4–6 KB. The fetch streaming reader splits large payloads across multiple reads, and our initial chunk.split('\n') approach discarded any fragment that didn't start with data: . We fixed it by implementing a proper line accumulator that holds incomplete lines across reads.
**Rate limits. **With five reasoning calls plus the main pipeline calls per analysis, a single run hits the Gemini API ten times. We added a threading.Semaphore(3) to cap concurrent calls per Cloud Run instance, exponential backoff on 429 errors (2s → 4s → 8s, up to three retries), and automatic fallback from gemini-2.5-flash to gemini-1.5-flash when the primary model's quota is exhausted.
**Making the agent feel like an agent. **Most AI tools are pipelines with a streaming UI layered on top — the "thinking" is fake and the output is predetermined. We wanted the branch decision to be visible and meaningful. The moment where the agent pauses mid-stream and asks a follow-up question — because it detected that the resume is too thin to work with — is the clearest demonstration of genuine adaptive behavior. Getting that UX right required multiple iterations on the clarification flow and the strategy badge display.
What we learned
Streaming architecture is underrated as a design constraint. Building the backend as an async generator that yields events forced us to think about every step as an observable unit — which made the whole system easier to debug, easier to explain, and more compelling to watch. The "thinking" panel emerged naturally from that architecture: once you're already streaming step events, streaming reasoning tokens is just another event type.
We also learned that the quality of the adaptive branching depends almost entirely on the quality of the evaluation prompt. Early versions of evaluate_resume_quality were too lenient — most resumes came back as strong regardless of content. Tightening the classification criteria and adding worked examples to the prompt made the branching actually meaningful.
What's next
Voice input for the role field — speak the job title, the agent handles everything else. Tracked applications with status updates fed back into Gemini for longitudinal insights: "Your domain-gap applications have a lower callback rate — your cover letter framing may be underselling your fundamentals." And a direct ATS export so the rewritten bullets land in the right format without copy-pasting.
Built With
- bullet-rewriting
- cover-letter-generation
- fastapi
- fit-analysis
- gemini-2.5-flash
- google-cloud
- httpx
- javascript
- jspdf
- pdfplumber
- python
- python-dotenv
- react18
- recharts
- serpapi
- step-reasoning-narration-gemini-thinkingconfig-?-live-reasoning-token-streaming-serpapi-?-live-job-description-retrieval-cloud-&-infrastructure-google-cloud-run-?-containerized-fastapi-backend-(docker
- supabase
- supabaseauth
- tailwindcss
- vercel
- vite
Log in or sign up for Devpost to join the conversation.