Skill

multi-platform-form-filling

careerai-agentspattern
Trigger

User needs to automatically fill job application forms across multiple ATS platforms

Version: 260420

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 Checks section 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 scoring
  • packages/adapters/src/direct/direct-company-adapter.ts (1765 lines): Generic form detection, field mapping, submit for company career pages
  • packages/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 verification
  • packages/types/src/job.ts: ScreeningQuestion interface

Interface

Trigger: User needs to automatically fill and submit job application forms across heterogeneous ATS platforms.

Inputs:

  • form_page: Playwright Page or Frame object pointed at the application form
  • user_profile: UserProfile (name, email, phone, linkedin_url, desired_locations, years_experience, skills, min_salary)
  • resume_path: Absolute file path to PDF resume for upload
  • screening_questions: Array of ScreeningQuestion { question, type, options?, required } detected from the form

Outputs:

  • filled_form: FilledApplication with jobId, profileId, resumePath, coverLetterText, screeningAnswers map, additionalFields map, and readyToSubmit boolean
  • answers_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 + input events after selectOption() (F34 fix). Discard dialog dismissal between steps (F23/F39). See skills/behavioral-anti-detection for timing layer.
  • Greenhouse: Standard HTML + OTP verification. GmailOtpExtractor polls inbox at 5s intervals, 120s timeout, strips HTML tags from OTP emails (F27). Segmented 8-box OTP input via keyboard.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 > 0 after setInputFiles(): 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: false triggers 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 via ATS_TYPE_TO_PLATFORM map.

Failure patterns resolved:

  • F9: Resume upload silent failure: verify files.length after setInputFiles()
  • 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 + input events
  • 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:

Quality Checks

  1. All required fields filled. Zero <input required> elements left empty at submit time. Scan form via CDP snapshot.
  2. Submission confirmation received. Either URL redirect to a thank-you page, or success-message DOM node present within 10s timeout.
  3. CAPTCHA detection triggers pause. Hitting a CAPTCHA writes captcha_seen event and pauses the session, not silent submit.
  4. Selector fallback order documented. Per platform (Greenhouse, Lever, Workday, Ashby, ATS-X), selectors listed in priority order in the skill.
  5. Resume upload succeeds. File upload element populated and confirmed (green check, file-name echo) before form submit.
  6. Session ban detection. HTTP 429 / 403 / CloudFront block pages trigger session abort, not retry-until-banned.

Visual Enrichment

MediumTypeDescription
RCMP grouped barSuccess rates by platform
FigmaSequence diagramDOM 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.