multi-platform-form-filling
User needs to automatically fill job application forms across multiple ATS platforms
Changelog
260420: multiple edits
- v_migrate: Changelog migrated from table to YYMMDD H3 format per versioning-standard rule 2 (V1.6 of skills upgrade plan)
- v6: Added license, sources per V6.1/V6.2 of skills upgrade plan.
- v1.5: Added
## Quality Checkssection per V1.5 of ~/vault/plans/2026-04-20-vault-skills-upgrade-plan.md
260403: Added Visual Enrichment section + self-improving-agent-patterns cross-reference
260331: Initial creation
Description
Three-layer form filling system that detects, maps, and answers application form fields across company career pages, LinkedIn Easy Apply, Greenhouse, and generic ATS platforms. The skill decomposes the problem into: (1) DOM-level field detection, (2) deterministic profile-to-field mapping via heuristics, and (3) AI-routed screening question answering with confidence scoring.
The system handles wildly different form implementations: from standard HTML forms on company career pages to LinkedIn’s closed shadow DOM modals to Greenhouse’s segmented OTP verification inputs: through platform-specific adapters that share a common detection and mapping core.
Key files:
packages/ai/src/form-filler.ts(188 lines): AI question routing and confidence scoringpackages/adapters/src/direct/direct-company-adapter.ts(1765 lines): Generic form detection, field mapping, submit for company career pagespackages/adapters/src/linkedin/easy-apply.ts(1956 lines): LinkedIn shadow DOM filling, multi-step modal navigation,resolveSmartAnswer()packages/adapters/src/greenhouse/greenhouse-adapter.ts: Greenhouse form fill + OTP verificationpackages/types/src/job.ts:ScreeningQuestioninterface
Interface
Trigger: User needs to automatically fill and submit job application forms across heterogeneous ATS platforms.
Inputs:
form_page: PlaywrightPageorFrameobject pointed at the application formuser_profile:UserProfile(name, email, phone, linkedin_url, desired_locations, years_experience, skills, min_salary)resume_path: Absolute file path to PDF resume for uploadscreening_questions: Array ofScreeningQuestion { question, type, options?, required }detected from the form
Outputs:
filled_form:FilledApplicationwith jobId, profileId, resumePath, coverLetterText, screeningAnswers map, additionalFields map, andreadyToSubmitbooleananswers_report:Map<string, FormAnswer>where each answer includes{ answer: string, confidence: number }(0.0-1.0)submission_result: Success/failure with evidence (screenshots, verification status)
Provenance
Three core patterns compose the system:
Pattern 1: Field Detection: detectFormFields() queries all visible inputs and resolves labels via 4-strategy cascade: <label for> element, aria-label, placeholder, name attribute. LinkedIn Easy Apply uses a different path: CdpDom.DOM.getDocument({ pierce: true }) traverses closed shadow DOM at the CDP protocol level, filtering by tag name and excluding search/hidden fields.
Pattern 2: Heuristic Mapping: resolveFieldValue() (Direct) and resolveSmartAnswer() (LinkedIn) map field labels to profile attributes deterministically. Covers 80%+ of fields with zero cost: name splits, email, phone, LinkedIn URL, location, years of experience, country variations, EEOC/EEO decline patterns, file upload sentinel, cover letter injection. AI is only invoked for screening questions that require reasoning.
Pattern 3: Screening Question AI Routing: Two-tier routing by isSimpleQuestion(). Simple questions (yes/no, select from options, short factual) use DeepSeek at temp 0.1. Complex questions (textarea, describe/explain/why, salary) use Claude/DeepSeek at temp 0.4. Both tiers return { answer: string, confidence: 0.0-1.0 }.
Platform-specific adapters:
- LinkedIn: Multi-step modal navigation through closed shadow DOM via CDP pierce. Dispatches both
change+inputevents afterselectOption()(F34 fix). Discard dialog dismissal between steps (F23/F39). See skills/behavioral-anti-detection for timing layer. - Greenhouse: Standard HTML + OTP verification.
GmailOtpExtractorpolls inbox at 5s intervals, 120s timeout, strips HTML tags from OTP emails (F27). Segmented 8-box OTP input viakeyboard.type()character distribution (F28). - Direct/Generic: Multi-strategy submit detection, blocked domain list prevents routing ATS URLs through generic adapter.
- File upload (all): Verify
files.length > 0aftersetInputFiles(): silent upload failures are common (F9).
Usage Notes
When to use this skill:
- Building any automated form-filling system that targets multiple web applications
- Need to handle heterogeneous form implementations (shadow DOM, iframes, React controlled components)
- Want AI-assisted screening question answers with confidence scoring
Key design decisions:
- Heuristic mapping first, AI second. The
resolveSmartAnswer()/resolveFieldValue()heuristics handle 80%+ of fields with zero latency and zero cost. AI is only invoked for screening questions that require reasoning. - Platform-specific adapters share no form-filling code. LinkedIn’s closed shadow DOM, Greenhouse’s OTP flow, and Direct’s generic scraping are fundamentally different problems. Attempting a unified abstraction would be fragile.
- Confidence scores enable downstream gating. The
FormAnswer.confidence(0.0-1.0) lets the submission pipeline decide whether to auto-submit or request human review. readyToSubmit: falsetriggers ATS hand-off. When LinkedIn discovers an external apply URL, the filled application signals the submit pipeline to re-route to the correct ATS adapter viaATS_TYPE_TO_PLATFORMmap.
Failure patterns resolved:
- F9: Resume upload silent failure: verify
files.lengthaftersetInputFiles() - F23: “Save application?” dialog blocking form:
getBoundingClientRect+ force click - F27: Greenhouse OTP HTML tags breaking regex: strip tags before extraction
- F28: Greenhouse segmented 8-box OTP input:
keyboard.type()character distribution - F34: LinkedIn select change not detected: dispatch
change+inputevents - F39: Easy Apply click race: click-and-verify loop with modal check between strategies
Integration with anti-detection:
- All field fills use topics/gaussian-behavioral-timing (never uniform random delays)
- Mouse movements follow Bezier curves via
humanClickAt()before clicking form elements - Label-reading pause between fields simulates human scanning behavior
- Inter-submission gaps:
gaussianRandom(480000, 180000)ms (mean 8 min, stddev 3 min) - Daily cap: 10 submissions per platform session
Related skills:
- skills/behavioral-anti-detection: timing, mouse, and pacing layer that wraps form filling
- skills/resume-tailoring: generates the
TailoredMaterialsconsumed by form filling - skills/cover-letter-generation: generates cover letter text for message/cover-letter fields
Quality Checks
- All required fields filled. Zero
<input required>elements left empty at submit time. Scan form via CDP snapshot. - Submission confirmation received. Either URL redirect to a thank-you page, or success-message DOM node present within 10s timeout.
- CAPTCHA detection triggers pause. Hitting a CAPTCHA writes
captcha_seenevent and pauses the session, not silent submit. - Selector fallback order documented. Per platform (Greenhouse, Lever, Workday, Ashby, ATS-X), selectors listed in priority order in the skill.
- Resume upload succeeds. File upload element populated and confirmed (green check, file-name echo) before form submit.
- Session ban detection. HTTP 429 / 403 / CloudFront block pages trigger session abort, not retry-until-banned.
Visual Enrichment
| Medium | Type | Description |
|---|---|---|
| R | CMP grouped bar | Success rates by platform |
| Figma | Sequence diagram | DOM detect -> map -> fill -> submit per platform |
Self-Improvement Cross-Reference
Pattern 2 (Skill Crystallization): platform adapters are crystallized procedures for each ATS. For the master reference on all 6 self-improvement patterns, see skills/self-improving-agent-patterns.