Inspiration
Searching for a job means a great many tabs, a great many forms, and long stretches of quiet in between. The tools built for it are capable, but they treat the whole process as data entry — spreadsheets, dashboards, and status columns. They help you keep records. They do not make the day any brighter.
We wanted to build something that actually stays with you. Not another dashboard you open, fill in, and close again, but a companion who lives on the page, pays attention to what you're reading, and makes the hunt a little less lonely. Someone with a face, a bit of warmth, and opinions worth hearing.
So we made Randy — a 48-pixel otter who peeks in from the edge of your browser.
What it does
Randy lives at the right-hand edge of LinkedIn, Handshake, and Greenhouse. He reads whatever posting you're on and every so often offers his honest read on it — usually a nudge in the right direction:
yeahhh this is kinda your lane
and now and then something more pointed, when a posting has earned it:
bro this is three jobs wearing a trench coat
Either way it is a real signal about a real listing. And when there is proper work to be done, he rolls up his sleeves and does it.
Hover over Randy and a chevron appears. Click it, and three actions unfold:
| Match | Weighs the posting against your actual experience and explains the verdict. 78% match — 80% quals but 76% prefs with a breakdown of preferences vs. qualifications and what works / what misses. |
| Track | Every application you confirm, recorded with company, title, date and status. The tracker fills itself as you go. |
| Tailor | A cover letter or résumé, written from your profile and this particular posting, compiled to PDF via LaTeX and dropped in your downloads. |
Alongside all of that, he is simply around:
- He notices things himself. Spend a moment on a posting and he will offer a thought unprompted — usually encouraging, occasionally a roast. No button required.
- He remembers to ask. Move on from a job and he checks whether you applied, then records it.
- He is an otter, not a widget. He hops out onto a log when you call him, leans out to speak with a synced typewriter bubble, grows charmingly indignant if you poke him five times, and slips back behind the edge of the screen once you are busy again.
Get him at randy-the-otter.vercel.app — download the ZIP, Load Unpacked in chrome://extensions, add your details in settings, and refresh your job tabs.
How we built it
Chrome extension (MV3) Flask backend
┌──────────────────────────┐ ┌─────────────────────────────┐
│ content scripts │ │ orchestrator (Strands) │
│ · sprite state machine │ HTTP │ ├─ roast specialist │
│ · scrapers ───────────────────────▶ ├─ match-score specialist │
│ · action menu │ │ ├─ cover-letter specialist │
│ service worker │ │ └─ résumé specialist │
│ · downloads, tabs │ │ │ │
└──────────────────────────┘ │ ▼ LaTeX → PDF │
└────────┼────────────────────┘
▼
Tectonic compile service (Docker)
Extension (Manifest V3). Randy is a small state machine — pose × mood × speaking with one-shot transitions for jumps and sulks — driving 22 hand-drawn animations at 48 pixels. His speech bubble types itself out in time with his mouth, and every element arrives and departs through a stepped pixel dissolve generated at build time.
Reading the page. LinkedIn is read straight from the DOM. Handshake renders its listings client-side, so a page-context interceptor captures the GraphQL responses and merges them with the DOM scrape — either source on its own leaves gaps.
Agents. A session-scoped orchestrator delegates to stateless specialists via the agents-as-tools pattern (Strands Agents + Gemini). Cover letter and résumé bypass the orchestrator and call their specialist directly, because an extra agent hop loses the invocation state the PDF handoff depends on.
Documents. The agent fills a LaTeX template from your profile, and a containerised Tectonic service (FastAPI + Docker) compiles it. Generation runs as a job: the POST returns a job_id, the service worker polls for status, and the finished PDF arrives quietly in your downloads.
Challenges we ran into
Handshake's rendering. No server-rendered description to scrape — we had to inject a page-context script to intercept GraphQL payloads and stitch them with DOM data without breaking CSP, then keep both sources in sync as the SPA navigates.
Making an otter feel alive. 22 sprites at 48px leaves no room for sloppy timing. Building a deterministic state machine for hops, speaking loops, and sulks, plus generating pixel dissolves at build time, took more iteration than the backend. Getting the typewriter, mouth, and dissolve to feel like one creature and not three layered effects was its own puzzle.
Agent orchestration vs. direct calls. The orchestrator is great for roasts and match scores, but an extra hop dropped the invocation state needed for the LaTeX handoff. We split the path — orchestrator for chat, direct specialist for docs — and had to keep session handling consistent across both.
LaTeX to PDF reliably. Templating is easy; compiling user-filled LaTeX safely in a container, escaping edge cases, and polling from an ephemeral MV3 service worker without losing the job took a full async job system.
Accomplishments that we're proud of
- 22 original sprite animations, a synced typewriter bubble, and dissolve transitions that make 48 pixels feel genuinely expressive.
- Seamless reading across three very different sites (LinkedIn, Handshake, Greenhouse) from a single extension.
- End-to-end PDF pipeline: profile → Gemini → LaTeX → Tectonic → download, with zero local setup for the user.
- Hosted backend + one-click download site — Randy works out of the box, no Python or API keys.
What we learned
- Presence beats features. The most loved part of Randy isn't Match/Track/Tailor — it's that he notices postings unprompted and remembers to ask if you applied. Being present on the page matters more than another filter.
- Strands' agents-as-tools is powerful, but stateless specialists plus explicit
invocation_stateforwarding matters when tools need user context like preferences and profile. - MV3 service workers are ephemeral — anything async (PDF polling, tab messaging) has to be defensive about wake/sleep. We learned to treat the background as a message forwarder, not a state holder.
- LaTeX is still the best way to make a résumé look like a résumé. Worth containerizing once and reusing.
- Small delights compound — poke reactions, hop animations, and a well-timed roast do more for retention than another dashboard column.
What's next for Randy The Otter
- More hunting grounds — Indeed, Wellfound, and Lever support so Randy follows you everywhere you search.
- Smarter tracking — auto-detect status changes, follow-up reminders, and a timeline view in the application tracker.
- Richer tailoring — iterative résumé diffs per posting instead of fresh generations, with inline highlights of what changed and why.
- Randy learns you — remember which roasts and matches you agreed with to calibrate taste over time, all on-device in
chrome.storage. - Chrome Web Store + Firefox port — remove the Developer Mode step so anyone can get Randy in one click.
- He gets a hat. Non-negotiable. Possibly seasonal. (more gamified benifits)
Built with: Chrome Extensions MV3 · Flask · Strands Agents · LaTeX / Tectonic · FastAPI
Randy is an otter, not a careers adviser — but he means well, and he is always pleased to see you. Take the roasts in the spirit they are offered.

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