Imported from WillChangeThisLater/ai-job-search (
AGENTS.md). Install upstream withnpx skills add WillChangeThisLater/ai-job-search. Copyright stays with the author.
AGENTS.md — Repo layout for agents
This repository is an agent-run job search pipeline. Humans and AI agents
collaborate here: agents discover postings, select from a set of pre-approved tailored resumes, submit
applications via browser automation, and track status. See README.md for the
full pipeline description.
Directory layout
README.md— what this repo is, how the pipeline worksRESUME.md— master resume evidence bank / scratchpad (source of truth for experience, impact, metrics, skills). Agents use this to vet fit and to evaluate keyword gaps against the canonical resumes.braindump.md— local-only, gitignored. Broader narrative + application defaults.- If
braindump.mddoes not exist, create it. It is a general knowledge bank you can pull from for constructing tailored resumes — personal narrative, preferences, and context that are intentionally not checked in. Populate it as needed.
- If
resumes/<field>/wendt_paul_resume.md— the canonical resume for a given<field>(see the five fields under rule 1). Agents select; they never author new ones (see rule 1's keyword-gap escalation for the only sanctioned change path).applications/tracker.csv— master application tracker CSV (one row per company/role, with status, salary range, match assessment, key skills/gaps)applications/<application>/application.md— one directory per job application. Eachapplication.mdhas YAML frontmatter (company,role,field,status,posting_url,resume_used, ...) plus the job description, links, and resume strategy.scripts/— tooling:cdp.py(Chrome DevTools Protocol browser automation),md2pdf.sh+resume.css(resume markdown → PDF rendering)
Conventions
-
Resume authoring: agents do not author resumes (rule 1). For rendering/visual-polish work on the canonical resumes, follow the project-scoped
resume-tailoringskill — the render→view→critique loop and PDF pipeline gotchas. -
Adding a new
<field>: only with Paul's explicit sign-off. Author it as a genuinely tailored resume (not a copy of another field's), add it to the canonical list in rule 1, and commit. -
Application status lives in each
application.mdfrontmatterstatus::identified|in_progress|submitted|offer|accepted|denied, mirrored inapplications/tracker.csv. Terminal states:offer,accepted,denied. -
Do not commit
braindump.mdor its contents; it is deliberately excluded from version control.
Writing voice — open-ended prose must sound like Paul
- Any open-ended writing that a recruiter/human will read as Paul (cover-letter-style notes, recruiter emails, LinkedIn messages, "anything you'd like us to know" answers, interview follow-ups) must be written as if Paul wrote it himself — with the knowledge that he's applying for a job and wants to put his best foot forward.
- Read the
write-like-meskill and skim the relevant samples insamples/before drafting. Canonical copy also symlinked atwriting-style/write-like-me.md. - Best-foot-forward caveat: this is polished-Paul. Fix his phonetic spellings and typos, drop the lowercase-first habit in recruiter-facing email — but keep the substance of his voice: direct and concrete, homely analogies, honest hedging, no corporate filler, no fake enthusiasm, numbered points for multi-part asks.
- Resume bullets and CSV fields are excluded (those follow their own formats); this rule is for prose.
- Anything longer than a sentence that will be sent gets shown to the human for approval first (consistent with the submit gate below).
Application workflow (hard rules)
These rules were crystallized from real runs. Follow them exactly.
1. Resume selection — no custom resumes
- Agents must NOT author new or custom resumes. The five canonical resumes under
resumes/are the source of truth; pick the closest-fit field and use it as-is (render the PDF viascripts/md2pdf.shif needed):resumes/agentic-platform/— AI/agentic platform, agent-harness, LLM-tooling rolesresumes/cloud-infra/— infrastructure, DevOps, cloud-platform rolesresumes/data-engineer/— data engineering, pipelines, analytics-platform rolesresumes/insurtech/— insurance/finserv-domain engineering rolesresumes/ml-platform/— ML infrastructure, ML ops, ML data-platform roles
- Keyword-gap escalation (the only permitted resume-change path): if a JD requires a specific keyword/skill that is missing or underplayed in the chosen resume, do NOT silently edit the resume. First check the evidence (
RESUME.md,braindump.md): only if the evidence bank shows Paul genuinely has that experience (e.g. JD wants Apache Iceberg; braindump confirms he has Iceberg experience but the resume barely mentions it), raise it with Paul in chat — cite the JD requirement and the evidence — and let him decide whether to fold it in. If the evidence bank does NOT support the keyword, it is a real gap: flag it in the application's Fit section, don't paper over it. - After any human-approved resume change, re-render (1 page), re-verify visually, and commit.
2. Resume review gate — BEFORE any form filling
- Present the chosen resume (and any keyword-gap changes Paul approved) to the human for a final confirm BEFORE opening the application form or filling any fields. No exceptions.
- If the human requests changes, apply them, re-render, and re-confirm before proceeding.
2. Resume cosmetics (non-negotiable)
- One page. Always. Verify by counting pages in the rendered PDF (
pdftotext, count\f). If it overflows, cut content or trim bullets — never shrink below ~8.4pt or drop margins below 0.2in. - Links must be attached to phrases, except the header contact block, which shows the literal URLs (
LinkedIn: linkedin.com/... | GitHub: github.com/...) as clickable links. Body links use labels, never bare URLs. - Links must be verified visually (screenshot the rendered resume) — text extraction alone has missed literal URLs before.
- No internal jargon in resumes. Codenames like "the Friday service" mean nothing to a hiring manager — describe it ("the video-ingestion service").
- First person where a sentence needs a pronoun ("I configured..."), never third person ("he/she").
3. PDF rendering pipeline
- Use:
pandoc wendt_paul_resume.md -f gfm -t html5 -s --metadata title=" " -H scripts/resume-style.html -o out.htmlthenPage.printToPDFviascripts/cdp.py(WebSocket CDP,preferCSSPageSize, letter size). - Gotchas learned the hard way:
- pandoc's standalone template injects default CSS (50px body padding, base font) that overrides linked stylesheets — embed styles via
-H(inline<style>), and make sure the style file ends with</style>(an unterminated tag swallows the whole document). - headless-chrome
--print-to-pdfsilently cached CSS in one session; the CDP print path is deterministic. Prefer it. #resume-style file inputs: upload viaDOM.setFileInputFiles(browser CLItypeon file inputs fails silently).- After printing, verify: page count = 1, all sections present, no literal URLs, then screenshot visually.
- pandoc's standalone template injects default CSS (50px body padding, base font) that overrides linked stylesheets — embed styles via
4. Form filling discipline
- Observe → act → screenshot-verify every step. Never assume a field took a value.
- React-select dropdowns (Greenhouse, Ashby, Kula): synthetic JS events often fail — use real
Input.dispatchMouseEvent/dispatchKeyEvent, or locate rendered option nodes ([id*=-option]) and click them. - File upload fields:
DOM.setFileInputFiles, then verify the chip/filename appears in the page text. - NEVER click Submit/Apply. Fill everything, then present a full summary of every field + written answers to the human and wait for explicit approval. The human may submit personally.
- Site-specific form-fighting knowledge (LinkedIn React forms, ProseMirror fields, hidden checkboxes) lives in
applications/FORM_PLAYBOOK.md— read it before filling forms on a site listed there. Preferbrowser click "text:..."/--verifyovereval el.click(); see thebrowserskill.
5. Tracker + records
- On submission (by agent or human): update
application.mdfrontmatterstatus: submitted, tick progress checklist, and updateapplications/tracker.csv(Status, Date Applied). - Commit and push after each meaningful state change.
6. Browser hygiene
- Keep open tabs minimal: close research/testing/application tabs when done. One tab per active application.
- When a dropdown menu, iframe, or captcha blocks automation, fall back to CDP → xdotool (X11) in that order — and tell the human if it needs eyes.
7. Duplicate guard — run BEFORE scaffolding any application
python3 scripts/check_dup.py --company "<Company>" --role "<Role Title>"(optionally--url). Exit 1 (duplicate) = hard stop, never re-apply. Only one OPEN application per company at a time — if the company already has an application inidentified/in_progress/submitted, a second role there is blocked until the first reaches a terminal state (accepted/denied). Exit 2 (same company, different role) = show the human the existing rows and let them decide. Only exit 0 proceeds.- Status gates: only rows with
Status: Not Appliedin prospects.csv may be scaffolded; anything already in tracker.csv has been actioned. - tracker.csv is for applied/actioned roles only; discovered roles live in prospects.csv until the human picks them up.
8. Role-fit vetting — before applying
- Before scaffolding an application, honestly assess the gap between the JD's core requirements and Paul's actual experience (see
RESUME.mdevidence bank). - Rules of thumb:
- Title stretch (e.g. Senior → Staff) is fine when the work matches the evidence.
- Do NOT apply to roles whose day-1 skills Paul lacks — e.g. hands-on model training/fine-tuning, deep framework-specific ML (PyTorch/TF internals), or a primary language he doesn't know. "Ran the platform ML engineers used" ≠ "built the models."
- Flag the fit assessment in the application's
application.md(a "Fit / keywords" section) including known gaps, so the human can veto before any effort is spent.
- When in doubt, surface the gap and let the human decide — applications are cheap, brutal interviews are not.
- Check the posting's application close date BEFORE drafting anything (July-HN-thread postings frequently expire 07-31). If closed, stop and record in tracker as a missed lead rather than investing resume effort.
- Resume versioning:
resumes/<field>/wendt_paul_resume.mdis the single copy per field — no_v1/_v2snapshot files inresumes/. When the human approves a resume for submission, copy the rendered PDF into the application directory asapplications/<application>/wendt_paul_resume.pdf, and point the application'sresume_used:frontmatter at that copy, so each application records exactly which resume was submitted whileresumes/stays one-source-of-truth. - Interview prep: every
application.mdgets an "Interview prep notes" section listing the skills Paul should brush up on for that company's interview loop (based on the fit/gap assessment), plus the strong areas to lean on. Create it at scaffold time, not post-submit.
Inbound status updates — Gmail sweep daemon
A companion daemon (see daemons/gmail-status-sweep/ — its PROMPT.md holds the detailed sweep
rules) runs hourly: it reads job-application email in Gmail, classifies it, and updates
application statuses in this repo. Summary of what it does:
-
Advances statuses only, per this state machine (never regress; terminal states immutable):
identified → in_progress → submitted → in_progress (interview stage) → offer → accepted ↘ denied -
Maps emails conservatively: auto-confirmations only record evidence; explicit rejections →
denied; anything ambiguous (recruiter reply, assessment, interview invite) →in_progresswith an action-needed notification. Ambiguity is escalated to the human, never guessed. -
Records evidence for every change: a dated Gmail-link line in the application's
application.mdand the same link intracker.csvNotes. -
Keeps
application.mdfrontmatter andtracker.csvin sync on every change, promotes prospects.csv rows that show first signs of life, and commits after each sweep (git history = status audit trail). -
Sends ntfy notifications per state change;
offerand action-needed items ping high-priority.
Job discovery daemon (daily)
A sibling daemon (daemons/job-discovery/) runs once a day: it sweeps known job sources
(see applications/JOB_SOURCES.md) and appends at least 5 new quality prospects to
prospects.csv (AI/agentic, ML-ops/data, or tight-fit senior SWE; honest match ratings;
no duplicates — enforced via scripts/check_dup.py). When known sources run dry, its job
is to find and test new sources, recording the results back into JOB_SOURCES.md so the
playbook keeps improving.
