CH·01 / Open-source agent

Preflight

An AI agent that fills job applications, with dry runs enforced inside the browser.

Year
2026
Built with
Python, Claude Code, Playwright

The problem

Applying for jobs is mostly form-filling: the same facts, typed into a different applicant tracking system every time. An AI agent can do that work. An agent that drives a real browser can also click the final Submit when it shouldn’t, answer a question with something you never said, or apply to the same job twice. Preflight is the pipeline I built to do the work, with guardrails that make it safe to run.

What it does

Preflight finds postings, filters and scores them against your profile with an LLM, tailors your resume and cover letter to each one, renders them as PDFs, and then hands each application to a browser agent that fills in the employer’s form.

  1. 01discoverJob boards, employer ATS boards and other career sites, with duplicates merged across sources.
  2. 02enrichFull descriptions and apply links: JSON-LD first, then CSS patterns, then an LLM only when both fail.
  3. 03scoreFree checks first (title, on-site wording, clearance, state limits), then a 1-10 fit score from the LLM.
  4. 04resolveFinds the employer's own application page, without an LLM.
  5. 05tailorRewrites the resume from your real experience; a validator and a second LLM pass check for invented facts.
  6. 06coverWrites the cover letter under the same rules.
  7. 07pdfRenders the resume and letter with headless Chromium.
  8. 08applyClaude Code drives a Chrome window per worker through a Playwright MCP server, dry run first.

Safety layers

The hard part is the last step. A dry run is meant to complete the whole form and stop before the final submit, and that stop is enforced in the page, not only requested in the prompt.

A guard script runs in every page and frame before the site’s own scripts. It reads the form’s step markers and blocks the final submit, whether by a click, form.submit() or requestSubmit(), while Next and Save & Continue pass on earlier steps. A hook refuses any JavaScript the agent writes itself during a dry run, so the agent cannot route around the guard.

A test application form for a Data Analyst role, filled in for the fictional applicant Jordan Rivera, with a red banner across the top reading: PREFLIGHT DRY RUN: final Submit blocked (Submit).
The guard blocking a final Submit on one of Preflight’s own test pages.

Each layer below covers a different way an application could go wrong. Paths are under src/preflight/.

Safety layers
LayerWhereWhat it prevents
Dry-run guardapply/helpers/dry_run_guard.jsThe final submit leaving the browser in a dry run, by click, form.submit() or requestSubmit().
Step parserapply/helpers/lib/step_markers.jsDisagreement about which step is final: the guard and the agent's page reader share one parser.
Dry-run hookapply/dry_run_hook.pyThe agent running JavaScript of its own in a dry run. The launcher tests the hook before every dry run.
Phone guardapply/phone_guard.pyTyping a phone number that is not yours or a reference's through the form tools.
Auditorapply/dry_run_audit.pyapply/form_audit.pyA wrong field going unnoticed: an LLM checks the filled page against your facts, independently of the agent.
Confirmation backstopapply/launcher.pyA false "applied", and a submission during a dry run going unnoticed.
Dedupededupe.pyapply/posting_identity.pyApplying twice to one job found on several sites, or to a posting that changed under the link.
Tool allow-listapply/launcher.pyAny tool outside the list, including sending email in a dry run.
Credentialsapply/helpers/account.pyapply/model_provider.pyOne shared password across sites, or an API key reaching a host it was not configured for.

The known gap, stated plainly: the guard does not see a page’s own script posting the form with fetch. The confirmation backstop and the independent audit catch that case afterwards, and a confirmation seen during a dry run sets the job aside for review.

Anti-detection, plainly

Job sites run bot detection, and an automated browser session gets blocked or served endless CAPTCHAs. Preflight includes measures so it can complete applications anyway:

  • The driver. A pinned Patchright build of the Playwright MCP server attaches to Chrome without the Runtime.enable call most detectors look for.
  • Natural input. The agent’s clicks move the mouse along a short, slightly curved path, pause, and hold the button for a human-length moment instead of jumping to the exact center of the element.
  • CAPTCHAs. Paid solvers run only with the operator’s own key. Cloudflare’s checkbox waits for a person to solve it in the visible window by default, and a challenge nobody can solve ends the attempt.
  • Proxy. An optional residential proxy for CAPTCHA-related traffic, with both exit IPs checked to match before the agent starts.

These exist so that a person applying to jobs for themselves is not locked out. The operator is responsible for following each site’s terms; Preflight does not create fake identities and only ever applies as you.

Testing

The suite has 805 tests and runs in under 30 seconds with no network, no API key and no real browser profile. It uses a fake OpenAI-compatible LLM server and a fictional applicant, and it runs the dry-run guard in headless Chromium against local pages, checking by the requests the server receives that the final submit never arrives while the allowed steps do. CI runs the suite on Linux (Python 3.11 to 3.13) and macOS. Nothing in CI or the test suite submits an application.

How I built it

I directed AI coding agents through the build with a test-first workflow, and had an independent AI reviewer check the work before it landed.

Honest note

  • Preflight began from ApplyPilot 0.3.0 by Pickle-Pixel (AGPL-3.0-only), whose pipeline shape and some of whose code it keeps. Most of its current code was written for Preflight.
  • The 50+ live applications were sent by my private predecessor system, not by this public repository.

Read more