Inspiration
Every tool in this space assumes the bottleneck is producing an application. It isn't. That takes ten minutes and always has.
The bottleneck is everything around it. Forty applications in limbo with no idea which are alive. No memory of what you actually did at your last job, so you rewrite your resume from memory, badly. And what breaks people isn't rejection — it's silence. You send work into a system that returns nothing, so you can't tell a bad resume from a bad channel from bad luck. Without a signal you can't improve, and the search stops being a process and becomes a mood.
The other thing that pushed me: hand a model a job description and a thin profile, tell it to produce a compelling match, and it will manufacture the match. That's not a bug a better model fixes — nothing in that setup separates a fact you supplied from one invented to fill a gap.
I wanted to build the opposite. A tool that visibly refuses, and is more useful for refusing.
What it does
TalentAgent operates a job search as a workflow rather than answering questions about one.
It keeps an evidence graph of what you've actually done, in your words. For each requirement in a posting it scores the coverage, then either writes a line backed by real evidence or refuses and sends a question back to you. It fills the employer's ATS form and stops. It reads your inbox and derives where each application stands.
Five steps, one chore, and only one of them is text.
How I built it
Python on Cloud Run, Gemini through the GenAI SDK on two tiers: Flash-Lite for labelling, Flash for judgement.
The load-bearing decision is where the model sits. Retrieval scores each requirement against your evidence and compares it to a threshold — arithmetic, outside the model. Only above that threshold is Gemini asked to phrase one of your own entries, and a line whose credit doesn't resolve fails schema validation. The model never decides whether a claim is allowed, only how to word one that already is.
The inbox works the same way. Gemini labels each message from a closed set; a transition table decides what that does to the application state. The model proposes, the table disposes — so it can't invent a state that doesn't exist.
Guardrails are mechanisms, not instructions. The clearest: the form filler cannot submit, because the page interface it drives has no submit method. There's no code path to disable. Gmail is the same shape — read-only scope, so no token it holds can send.
289 tests, and a socket guard that fails any test touching the network.
Challenges
The hardest problem wasn't technical. I'd built a large, well-specified system whose running product quietly didn't do what the docs said.
An audit late in the build found Gemini was never being called. The client existed but was wired to a health-check boolean; extraction was splitting lines by length. Nobody noticed because the transport caught every exception and returned a plausible fake — which also hid that the tier-1 model name didn't exist. The fix was making it raise. Everything else followed from being able to see failures.
Two things were worse than broken because they were confidently wrong. An endpoint turned a GitHub username into "Built core services and infrastructure for {user}/{repo}", stamped it verifiable, and wrote it to the graph. The form-fill endpoint reported 100% completion without executing anything. And the status endpoint claimed all seven guardrails active while one had a test marked "not yet enforced".
Deleting the first, wiring the second to the real executor, and making the third report pending cost the demo a feature and made it look worse. It was the most important work in the project.
What I learned
A refusal is a feature, and it's hard to keep. The pressure to fill a gap with something plausible exists at every layer — in the model, in the API design, and most of all in the person building the demo the night before a deadline.
The only guardrails that survive a refactor are absent capabilities, not checks. "It has no submit method" holds. "It checks whether it should submit" doesn't.
And silent fallbacks are how a system lies without anyone deciding to lie. Every fabrication here traced back to an exception handler returning something plausible instead of raising.
What's next
Every transition writes an outcome row. That log is the input to the part that answers the question this started from — why the callbacks aren't coming. The log is written; the analyst that reads it isn't built yet.
Then eligibility. If you need visa sponsorship, the most decision-relevant fact about a role is whether the employer sponsors, and nobody publishes it. It's in public filings. It's just never put in front of you when you need it.
Built With
- css
- docker
- firestore
- gemini
- github-actions
- gmail-api
- google-cloud
- google-cloud-run
- google-genai-sdk
- html
- javascript
- lxml
- mermaid
- mkdocs
- mypy
- oauth2
- pydantic
- pypdf
- pytest
- python
- rest-api
- ruff
Log in or sign up for Devpost to join the conversation.