-
-
Positioning questions with a recommended pick already selected — agreeing is one click, disagreeing is two
-
Every underlined phrase is cited — click it and the fact behind it appears. When a fact is missing, the agent asks instead of inventing.
-
Requirements judged against your facts, with how firmly each is worded — and an apply-or-skip advisory before you write.
-
The fact bank: every fact carries the verbatim snippet of your own text it came from.
-
The pipeline — what's in flight, what's gone quiet, and the next interview with one-click calendar export.
Inspiration
I spent couple months applying to real jobs and kept a log of every place the work went wrong. Not the tedium — the failures. A form that gave me a single-line input for a question that wanted a paragraph. A posting whose one hard requirement I couldn't meet, discovered after I'd written four answers. Four separate answers that all reached for the same story because I had one good one. A general model writing me a fluent sentence about a system I had never touched, which I nearly sent.
By the end the log had more entries about judgment than about typing. That was the finding: the slow part of an application isn't writing, it's deciding — what leads, what gets named, whether to apply at all — and those are decisions a model cannot make for you but can absolutely help you make. Find a Job is that log turned into software.
What it does
An agent that fills in job applications the way a good friend would: it reads your resume once, reads the posting, and writes each answer out of things you have actually done.
- A profile that cites itself. Drop in a resume PDF or paste notes; the agent splits it into atomic facts, each carrying the verbatim snippet of your own text it came from. Nothing it writes about you later can point anywhere else. Alongside the facts sit typed standard answers — work authorization, notice period, locations, compensation — the things only you know, asked once and reused, and a list of gaps: what the agent knows it does not know.
- The posting, read honestly. Paste a URL and it comes back through the Ashby, Greenhouse or Lever public job APIs — company and role clean, not scraped out of a page. Every requirement gets a verdict against your facts (met / not met / unclear), a read of how firmly it is worded — an explicit gate, a published escape clause, or silence — and when one is a firm no, an advisory that says so before you write four answers.
- The form, as it actually is. Paste the questions or drop in a screenshot of the form; the agent reads it multimodally and records each field's limit and control type, because a 150-word textarea and a one-line input are not asking for the same answer.
- Drafting you can check. Every factual sentence carries a citation. Select an underlined phrase and the fact behind it appears. What is not underlined is the agent's own prose and looks like it — no borrowed authority.
- It asks instead of inventing. When an answer needs something that isn't in your facts, the draft stops and puts the question to you rather than filling the hole with something plausible.
- It learns your voice. Each time you rewrite one of its sentences, the difference between what it wrote and what you saved is distilled into a durable rule and applied to every later draft.
- A pipeline, and the calendar. Where every application is, what has gone quiet, what
interview is next — and an
.icsexport so the round lands in whatever calendar you live in. - The interview, from the email that booked it. Paste the scheduling notice; it reads which round this is, when, and who is on the call, and writes a prep brief for that round against this posting — likely topics, questions to prepare with which of your own facts answers each, questions to ask back, your claims to rehearse verbatim, and the unmet gate that is going to come up. If the notice says "next Thursday" it does not invent a date; it asks.
It deliberately does not submit anything. You read it, you paste it, you send it. An agent that presses submit on your behalf is not a partner; it's a liability with your name on it.
The clarify loop — why this is a Collaborative Partner
The first version drafted grounded answers that were still flat: true sentences, no argument. The facts were right and the positioning was absent, because positioning is a decision and the agent was making it silently and badly.
So there is now a step between "here is a question" and "here is a draft". Press Set up this answer and the agent asks up to four positioning questions — which half of your background should lead? does the four-month gap get named? — each with a stated reason for asking, two to four concrete options, and a recommended pick already selected. There is always an "In my own words" override.
Three properties make it a partnership rather than a quiz:
- It only asks what only you can settle. Anything it could resolve from your facts or the posting, it resolves. Judgment calls come to you.
- It arrives with an opinion. The recommendation is pre-selected, so the fast path is glance and draft, and the slow path is to disagree. A partner who only asks questions is another form of work.
- It is skippable, and it learns. A draft runs fine with no clarify answers at all. And whatever you rewrite afterwards feeds the voice rules, so the next draft starts closer.
The same posture runs through the rest: the gates advisory hands you a decision instead of quietly writing around it, and the ask-the-human card is what happens when grounding fails — the agent would rather stop and ask than write something you'd have to catch.
How we built it
One Next.js app on Cloud Run. No second service, no queue, no worker — every model call happens inside the request that asked for it.
- Gemini 3.7 Flash through Genkit, nine flows:
profileIngest,reconcileFacts,jobInterpret,formParse,clarifyDraft,answerDraft,feedbackDistill,interviewInterpret,prepBrief. Each has its own thinking budget set deliberately rather than uniformly (256 where the work is close to transcription, 1024 where it is judgment — the gate verdicts and the prep brief). They reach the model through one function, so the model, the retry policy and the sampling settings are decided once. - Temperature is always sent as 0. Left unset, Gemini's default was high enough that ten
identical
formParseruns over one eight-field form returned seven, eight or nine questions — and once, two. A product whose claim is grounded, not credulous cannot have its groundedness depend on a sample. - Three grounding guards, in code, after every generation. The schema checks shape; shape is where its knowledge ends. Our own code checks that the stated word or character limit is met, that every cited span appears verbatim in the answer text, and that every fact id names a fact actually passed in. A failure gets exactly one correction carrying the rejected attempt back with it; still wrong is a real failure, surfaced rather than smoothed over. A fourth guard sits at the edge: the UI renders a citation as a followable underline only when its fact resolves, so a citation the reader cannot follow never renders as verified. And a fifth filters the interview brief's rehearsal lines down to claims actually in the profile — nothing the model composed gets handed to somebody to say out loud in an interview.
- Google Cloud throughout. Cloud Run (deployed from source — no Dockerfile, buildpacks handle
it), Firestore for all state, Firebase Auth for identity. Every route handler begins with
requireUser; data is addressed asusers/{uid}/…, so scoping is structural rather than a filter someone has to remember.firestore.rulesdenies every direct client read and write — the browser never touches Firestore, and the server reaches it through the Admin SDK. - 746 tests, four days, built subagent-by-subagent with an adversarial review pass on every task before it landed.
Challenges we ran into
LinkedIn serves nothing to a non-browser client. It was the obvious first ingestion path and
it is a dead end — no amount of retrying fixes a page that isn't there. The answer was to stop
treating fetch as the happy path: three ATS adapters that read public JSON job APIs (Ashby,
Greenhouse, Lever), a guarded generic fetch for everything else, and paste as a first-class
path rather than a fallback for the unlucky. A blocked fetch raises a typed error that becomes
a 422 with needPaste: true, and the wizard asks for the text. It is never a silent failure and
never a guess.
The generic fetcher was an SSRF hole. It fetches whatever URL the user types, from a server
sitting inside a private network with a cloud metadata endpoint on it. Security review caught it.
Closing it took more than a blocklist: protocol and port allowlist, IPv4 private and link-local
ranges, IPv6 loopback / unique-local / link-local plus IPv4-mapped literals, a dotless-hostname
rule, and the trailing-dot bypass — localhost. is a legal fully-qualified spelling of the same
name that walks straight past a dotless check. Redirects are followed by hand, three hops
max, with every hop re-checked, because a public host that bounces you at an internal one is the
same attack with an extra step.
Extraction variance is a serving-side property, not a prompt problem. The same input, the same prompt, ten runs, materially different structure. Pinning temperature to 0 made it reproducible; it did not make it correct. The correctness had to come from verifying the output in our own code afterwards — which is how the grounding guards ended up as code rather than as another paragraph of instructions.
Accomplishments that we're proud of
- Grounding that is enforced, not requested. Every claim about the citations is checked by a test, not asserted in a prompt. The spans are verified verbatim; the fact ids are verified to exist; the limit is counted.
- The agent asks instead of inventing. Watching it refuse to write a sentence and hand back a question was the moment the product stopped being a text generator.
- The clarify loop — the fix for shallow drafts turned out to be the feature, and it is exactly the thing the Collaborative Partner category is asking about.
- 746 tests in four days, with review passes that found real bugs (the SSRF, an arithmetic error in our own sample data on the screen whose whole claim is that claims can be checked).
- A server-only data path. Deny-all Firestore rules that cost the app nothing, because no browser in it ever asks.
What we learned
Twelve findings from a week of real applications, generalized. Each earned an entry in the log before it earned a line of code.
- Control type is the unwritten length spec. A one-line input and a textarea ask for different answers to identical words — read the form's controls, not only its labels.
- Artifact scope gates technique. Whether a document attaches to the requisition or to the profile decides whether you may quote the posting, name the team, or address a stated minimum.
- An unmet minimum is a decision, not a detail. Surface it before drafting, with a read of how firmly it is worded, so the human can choose to skip rather than discover it at the end.
- Models understate their own owner's work. People compress years into a feeling; the specific number is almost always present in the primary source and almost never in the summary they hand you. Go to the source.
- Never cache generated prose; cache the structured intake. The same events carry a different spine for a different role — re-frame per posting, don't re-serve.
- Extraction variance is a serving-side property. Temperature is hygiene, not the fix; the fix is verifying the output in code you control.
- A citation you cannot verify in code is a vibe. If the span isn't checked verbatim and the id isn't checked to exist, the underline is just borrowed authority.
- Relevance is a relation, not a property. Rank the fact bank per posting; never persist a "top projects" list. Store why something was demoted — that reason is what promotes it somewhere else.
- Independently drafted answers converge. Same facts, same generator, same best story. Assign each open field a distinct argument and check across fields for repeated reasoning, not repeated wording.
- Ask once, reuse forever. The answers only the human holds — authorization, dates, location, compensation — belong in a typed store, not in a prompt repeated per application.
- A blocked fetch must be a visible refusal. Pasting is a first-class path; guessing at content you could not retrieve is the one unrecoverable failure.
- Bridge on the shared principle, not the shared word. The honest analogue for a missing technology is a matching underlying mechanism — never the same thing renamed.
What's next for Find a Job
- Interview prep, beyond the first brief. Paste the scheduling email and you get a typed round and a prep brief today. Still to come: reading a screenshot of the notice, a mock interview you can actually talk to, and answering the round's open questions in place rather than only seeing them.
- An AWS port, to check that nothing here is load-bearing on one cloud. The flows are behind a single call site precisely so that this is a swap and not a rewrite.
- Deeper form coverage — more ATS adapters, and a per-ATS table of which documents attach to the profile versus the requisition, which finding #2 says is the thing that decides technique.
Built With
- cloud-run
- firebase-auth
- firestore
- gemini
- genkit
- google-cloud
- nextjs
- node.js
- react
- tailwindcss
- typescript
- vitest


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