I built a robot that applies for jobs. The hard part was proving it worked.
DEV Community

I built a robot that applies for jobs. The hard part was proving it worked.

The demo was never the problem. A browser agent that opens a job posting, reads the form, writes your history into it and clicks submit is a weekend of work now. It looks like magic in a screen recording. You can film one this afternoon. The problem is the sentence you have to say afterwards: it worked. I spent one full day, eight hours and seventeen minutes, and twelve real applications on real postings, chasing four failures. I landed none of them. Every experiment cost about fifty-six minutes, because most of that was waiting for a deploy to reach the machines that run the applications before the test could even begin. Three separate fixes to one small function shipped fifty-five minutes apart, then fifty-four minutes apart. All three were properties of the same HTML element, and all three were findable in a single pass with a browser open in front of me. The whole time, a local test rig was running on my own laptop. Thirty-six hours of uptime. Its log recorded zero bytes during my entire working day. The actual bug, when I finally opened the page like a person instead of reading logs like a detective, took two minutes. A field I was reading as a textarea was an input . Production is a verifier, not a debugger That is the rule I got out of the day, and it cost enough that I now enforce it in writing. A production run answers exactly one question: does this hold in the real world. It answers that question expensively, slowly, and once. Using it to find a bug means paying full price for every guess, and you will guess a lot, because the feedback arrives an hour later stripped of everything you needed. A local rig answers a hundred cheap questions badly and one expensive question not at all. That is the correct division. Look at the page, fix it locally, verify it locally, and only then spend a real run on the things a laptop structurally cannot answer: capacity, queueing, anti-bot behaviour, the employer's actual response. The order matters more than the tooling. Look, then fix, then verify, then spend. I had it backwards for a day. The instrument is the thing that lies Here is the deeper version, and it is the single most useful idea I have taken out of building this. Most of my hardest bugs were not bugs in the system. They were bugs in the thing measuring the system. - A form field reported as filled, because the check read back our own typing rather than asking the widget what it had committed. Seventeen fields, all confirmed, all empty. - A metric that read zero for a feature that was working perfectly, because the measurement armed itself eighteen seconds after the event it was supposed to measure. - A watchdog that killed live sessions for going quiet, when quiet was what a long submission looks like. Its own reset then removed the signal that would have told it so, which made it produce the next reason to fire. - A daily alarm that paged me at full severity for an incident I had already fixed the previous afternoon, because it computed its rate over a rolling twenty-four hours and the burst was still inside the window. Every one of those looked exactly like a broken product and was in fact a broken measurement. The failure mode is symmetrical and horrible: you fix things that were never wrong, and you ignore things that are. So I wrote a rule and put it where I cannot avoid reading it: An absence is not evidence until you have proved you were watching. Prove the instrument was armed at all, prove it was armed before the event and still armed after, and prove the mechanism works when a human does it by hand. If you cannot prove all three, the correct word is unobserved, not absent. The distance between absent and unobserved is where most of my wasted engineering time has lived. Why this ends up being the product Once you have been fooled by your own telemetry enough times, you stop trusting any signal you generate yourself. And the moment you apply that to a job application tool, the entire design falls out of it. Our own click is not evidence. Our own screenshot is not evidence. Our own status field is not evidence. The only thing that proves an application exists is the company's own system replying, and that reply arrives at an address we control, gets classified, and is the only thing that moves an application to sent. It is a harder product to build and a much easier product to trust, and it means the number on the dashboard is the one number I cannot fake for myself. That is not a marketing position. It is what is left after you stop believing your own instruments. I am Ava Bagherzadeh, building AI Applyd as a solo founder. It scores, tailors and submits applications on the company's own hiring system, and only counts one as sent when that company confirms it. Interviews on your calendar, not rejection emails in your inbox. Top comments (1) The rule you put on the wall already exists in the monitoring world with a mechanism attached, which might save you from rediscovering the rest of it. It's called a watchdog alert, or a dead man's switch depending on who you ask. The implementation is an alert rule that always fires, routed to an external service that pages you when the notifications stop arriving. kube-prometheus-stack ships one named Watchdog by default. It exists for exactly your reason: an alerting pipeline that has quietly died looks identical to a system with nothing wrong. Your three-part proof, armed before, armed after, works by hand, is that pattern written out longhand. Your zero-reading metric has a name too. In Prometheus terms, it's the difference between a series reporting zero and a series being absent. Zero means the exporter observed a value of zero. Absent means there's no observation at all for that label set. Collapsing the second into the first is what hides collector failures, relabelling mistakes and vanished targets. absent_over_time() exists to alert on that second case specifically. Your eighteen-second arming gap is the absent case wearing the zero case's clothes.The daily alarm is the most solved of the four. That's reset time, and the fix is multi-window, multi-burn-rate alerting from the SRE Workbook, chapter 5. Pair the long window with a short one, roughly a twelfth of its length, and require both to breach before it pages. The short window exists purely so the alert stops when the burst does, instead of riding out the remainder of the long window. That would have saved you the morning page for the incident you'd already fixed. Now the part I'd push on, because it follows from your own rule. Your design says the company's reply is the only evidence. I agree with the direction. But that reply is an instrument too, and it's one you control even less than the ones that fooled you. ATS confirmation behaviour isn't uniform. Some systems send nothing. Some send on save rather than submit. Some are delayed by hours, and some land in spam, which to a classifier is indistinguishable from never having arrived. Apply your own standard to it. If you can't prove the mailbox was reachable, the classifier armed, and the sender not silently dropped, then a missing confirmation is unobserved, not unsent. Which gives the number you say you can't fake for yourself a specific failure mode: it undercounts quietly, and undercounting reads as conservatism. That's what makes it hard to catch. The fix is the one you already applied everywhere else. Send a known-good application through a known ATS on a schedule and watch for the confirmation to come back. If that heartbeat stops, you know your classifier has gone deaf before a customer does. A dead man's switch for your own verifier.

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.