Inspiration

As a third-year AI/ML student deep in internship season, I was living the exact problem this project solves: paste a job description, tailor my resume by hand, retype the same contact details into yet another company's application form, draft a cold email if I could find a contact, then manually track a growing spreadsheet of "did I hear back yet?" across a dozen open applications. None of these steps is individually hard — the pain is doing all of them, for every role, every day, and remembering to follow up days later without forgetting. That's exactly the kind of cross-system busywork a chatbot can't fix, because the friction lives in the handoffs between steps, not inside any single conversation.

What it does

Paste a job posting — as text or a link — and the agent takes over the pipeline end-to-end: it parses the real posting (fetching the actual page content, not guessing from a URL), tailors your resume against it while honestly flagging any skills you genuinely don't have rather than fabricating them, and generates a real downloadable PDF. If a link was provided, it autofills the application form on supported ATS platforms (starting with Greenhouse) using a headless browser, clearly reporting which fields it filled and which need you. If — and only if — a real recruiter contact actually appears in the posting, it drafts outreach email for your approval; it never invents a contact address, since most modern ATS postings simply don't have one. You confirm when you've finished any remaining fields and actually submitted the application — the agent never assumes this on its own. From there, it monitors your Gmail for replies tied to that application, classifies them, and autonomously drafts the right follow-up days later.

How we built it

The core is a Google ADK agent (root_agent) wired to a toolbox of Python functions — JD parsing, resume tailoring, PDF generation, Playwright-driven form autofill, Gmail send/read, and Firestore state tracking — with Gemini providing the reasoning at each step. A FastAPI backend exposes two distinct paths: reasoning-heavy actions (parsing a JD, deciding what to do next) run through an ADK Runner, letting the agent itself choose the tool sequence, while pure state transitions (confirming a submission, listing applications) call tool functions directly, since routing deterministic writes through an LLM would just add latency for no benefit. The frontend is React, with Firebase Authentication for multi-tenant accounts and a large chat interface as the primary way to interact with the agent, alongside a structured dashboard for browsing applications. Firestore holds application state, Cloud Storage holds generated resume PDFs, and the whole thing is built to deploy on Cloud Run.

Challenges we ran into

Getting ADK's Runner correctly wired up took some real trial and error — early on, the whole "create an application" flow was quietly bypassing the agent entirely, calling parse_jd → tailor_resume → save_application as hardcoded Python instead of letting the agent decide that sequence, which would have undercut the entire "autonomous agent" premise if it had shipped that way. We also learned the hard way that Gemini can't browse a URL just because you paste one into a prompt — that required an explicit fetch-then-parse step. Designing Firestore's schema to honestly separate what the agent filled in a form from what the candidate filled in manually, and to never assume a form was submitted unless the candidate explicitly said so, took real care — the agent has no way to know unless it's told. And partway through, GitHub's push protection caught real Gmail OAuth tokens and a GCP API key that had ended up committed locally — a genuinely useful catch that meant rotating every exposed credential and rebuilding git history clean before anything went public.

Accomplishments that we're proud of

The honesty constraints throughout are the thing we're most proud of: the agent never fabricates a skill the candidate doesn't have, never invents a recruiter contact that isn't in the posting, and never claims a form was submitted unless a human explicitly confirms it. It would have been easy to fake full autonomy for a flashier demo — instead the agent is transparent about exactly where it hands control back to the candidate, and that felt like the more honest (and ultimately more useful) thing to build.

What we learned

The gap between "an app that calls an LLM" and "an agent that's genuinely autonomous" is real and easy to miss — it's not enough to use Gemini somewhere in your code; the orchestration decisions themselves need to flow through the agent for it to count as agentic reasoning rather than a scripted pipeline with an LLM sprinkled in. We also learned that real-world ATS platforms are far more heterogeneous than they first appear, which pushed us toward a tiered, honest design — autofill what's reliably identifiable, hand off what isn't, and never pretend otherwise.

What's next for Google ATAH Internship Agent

Expanding autofill beyond Greenhouse to Lever, Ashby, and Workday; a vision-based fallback (screenshot + Gemini reasoning) for application pages that don't match any known ATS pattern; letting the agent proactively discover matching roles via public job-search APIs instead of only working from a pasted posting; and a small retrieval-backed answer bank so free-text screening questions get grounded, consistent responses drawn from previously-approved answers instead of being regenerated from scratch every time.

Built With

Share this project:

Updates

Submission history