Hybrid Browser AI Agent for LinkedIn Easy Apply
A Python-based browser automation AI agent built with Playwright, a Finite State Machine (FSM), Page Object Model (POM), and an LLM-driven Semantic Matching & Form Interpretation Engine with a strict Human-in-the-Loop (HITL) approval gate.
Architecture Overview
d:\Gravity\
├── config/
│ ├── candidate_profile.json # Single source of truth for candidate data
│ └── settings.py # Pydantic BaseSettings (.env loader)
├── models/
│ ├── profile.py # Pydantic models for Candidate Profile
│ ├── job.py # JobCardSummary and JobDetails schemas
│ ├── evaluation.py # MatchBreakdown and JobEvaluationResult schemas
│ ├── form.py # FormField, FormStep, ApplicationSummary schemas
│ └── application.py # Database ApplicationRecord and Status
├── services/
│ ├── profile_loader.py # Profile reader & dynamic field calculation
│ ├── db_service.py # SQLite/PostgreSQL persistence & deduplication
│ ├── llm_service.py # Multi-backend LLM client & NLP heuristic engine
│ └── resume_service.py # Resume path manager & reportlab PDF generator
├── automation/
│ └── pages/
│ ├── base_page.py # Playwright POM base with anti-detection & typing jitter
│ ├── login_page.py # Auth state verification & manual checkpoint polling
│ ├── job_search_page.py # Easy Apply search navigation & job card extraction
│ ├── job_details_page.py # Description extraction & Easy Apply launcher
│ └── easy_apply_modal.py # Modal step traversal, dynamic filling, review
├── agents/
│ ├── job_evaluator.py # LLM semantic fit scorer & experience analysis
│ ├── form_agent.py # Dynamic form question interpreter & HITL routing
│ └── fsm_runner.py # Finite State Machine orchestrator
├── tests/ # Pytest automated test suite
├── main.py # Rich CLI runner & dashboard
├── requirements.txt # Python dependencies
└── .env.example # Example environment variables
Finite State Machine (FSM) Lifecycle
stateDiagram-v2
[*] --> INIT
INIT --> LAUNCH_BROWSER: Load profile & init database
LAUNCH_BROWSER --> CHECK_LOGIN: Launch persistent Chromium context
CHECK_LOGIN --> CHECK_LOGIN: Pause & wait if CAPTCHA / 2FA / Login needed
CHECK_LOGIN --> SEARCH_JOBS: Authenticated
SEARCH_JOBS --> EXTRACT_CARDS: Search with f_AL=true (Easy Apply)
EXTRACT_CARDS --> EVALUATE_FIT: Filter out applied jobs
EVALUATE_FIT --> NEXT_JOB: Score < min_score OR Experience Reject
EVALUATE_FIT --> FILL_FORM: Score >= min_score -> Launch Modal
FILL_FORM --> AWAIT_APPROVAL: Reached final review step
AWAIT_APPROVAL --> SUBMIT: User Approved [y]
AWAIT_APPROVAL --> NEXT_JOB: User Skipped [n]
SUBMIT --> TRACK: Click submit
TRACK --> NEXT_JOB: Persist to applications.db
NEXT_JOB --> EXTRACT_CARDS: Next unapplied card
NEXT_JOB --> [*]: Max jobs reached or finished
Key Features & Adherence to Requirements
-
Finite State Machine & Page Object Model:
- Clean separation between Playwright UI interactions (
automation/pages/), agent decision logic (agents/), data storage (services/), and schema contracts (models/).
- Clean separation between Playwright UI interactions (
-
Stealth & Persistent Browser Context:
- Uses Chromium in non-headless mode by default (
HEADLESS=false). - Uses persistent profile directory (
.browser_context/) to preserve cookies, sessions, and tokens. - Zero Credential Storage: LinkedIn passwords are never stored in code,
.env, or logs. - No Automated Solvers: If LinkedIn prompts with 2FA, OTP, or CAPTCHA, the agent halts, prints an alert to the terminal, and waits for manual completion in the open browser before resuming.
- Uses Chromium in non-headless mode by default (
-
Candidate Profile as Single Source of Truth:
- Located at
config/candidate_profile.json. - All downstream calculations (compensation, experience years, notice period, skill lookups) dynamically read from this profile without hardcoded values.
- Located at
-
Semantic LLM Fit Scoring:
- Evaluates:
- Experience Match: Candidate has 4 years; if job asks for 5–8 years, score is downgraded proportionally; if >8 years or Director/Lead, hard rejection.
- Skill Match: Semantic clustering recognizing synonyms (e.g. RestAssured/Postman <-> API testing, Selenium/Playwright <-> Web automation).
- Role Match: Checks relevance against preferred roles (QA Automation Engineer, SDET, etc.).
- Skips postings when
overall_score < minimum_match_score(70%).
- Evaluates:
-
Dynamic Form Filling:
- Maps standard personal details, current/expected CTC, 60 days notice period, and skill-specific experience years.
- Handles dropdowns, radio groups, and file uploads.
- Flags sensitive/ambiguous declarations (clearance, disability, citizenship) for explicit HITL prompt.
-
Human-in-the-Loop (HITL) Gate:
- Pauses on the final review step of the modal.
- Renders a Rich summary table (Job Title, Company, Match Score, File Uploaded).
- Requires explicit user approval (
[y] Approve & Submit / [n] Skip / [q] Quit) before clicking the submit button.
-
Application Tracking & Deduplication:
- Async SQLite database (
applications.db) with anapplicationstable. - Queries by
job_idorjob_url+companybefore opening any job card to avoid duplicates.
- Async SQLite database (
Installation & Setup
-
Install Dependencies:
pip install -r requirements.txt
-
Initialize Playwright Browser:
playwright install chromium
-
Configure Environment (Optional):
cp .env.example .env
Add your
OPENAI_API_KEYorGEMINI_API_KEYif you want live cloud LLM reasoning. If left empty, the built-in deterministic NLP extraction and semantic scoring engine will run offline automatically.
Usage Guide
1. Dry Run (Recommended for testing without submitting)
Simulates the workflow, extracts cards, computes fit scores, fills forms up to the final review step, and tests the HITL prompt without submitting:
python main.py --keyword "QA Automation Engineer" --location "Hyderabad" --max-jobs 3 --dry-run
2. Live Apply Mode
Executes the live workflow with non-headless browser:
python main.py --keyword "SDET" --location "Hyderabad" --min-score 70 --max-jobs 5
3. CLI Command Options
--keyword Job title or search keyword (default: "QA Automation Engineer")
--location Search location (default: "Hyderabad")
--max-jobs Maximum applications to process (default: 10)
--min-score Minimum fit score required to proceed (default: 70.0)
--headless Run Chromium in headless mode (default: False)
--dry-run Fill forms to review step without clicking final submit
--auto-approve Bypass interactive HITL prompt at review step
--profile Custom path to candidate_profile.json
--log-level Logging level [DEBUG, INFO, WARNING, ERROR] (default: INFO)
Running the Automated Test Suite
Execute pytest across all test modules:
pytest tests/ -v
Metadata
Release files for jobagent 1.0.13
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| jobagent-1.0.13.tar.gz | 123.1 kB | Without provenance | Details for jobagent-1.0.13.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| jobagent-1.0.13-py3-none-any.whl | Python 3 | none | any | Details for jobagent-1.0.13-py3-none-any.whl |
Total release size: 256.3 kB
Release files / jobagent-1.0.13.tar.gz
| Download URL | jobagent-1.0.13.tar.gz |
|---|---|
| Size | 123.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9fd0b5b0f34eee3e46995113395b5783f4340d33acc270db4a18f169df2fb4b8
|
|
BLAKE2b-256 checksum How to use checksums |
715025cdb091e7c1a0c2c1e46e995fe0b7dfe0eda31079841c84d2212dc9dd7c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / jobagent-1.0.13-py3-none-any.whl
| Download URL | jobagent-1.0.13-py3-none-any.whl |
|---|---|
| Size | 133.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f6894dc3614558a011824a9a7fffcd8262aed05a95b91279ac2c54a08622d522
|
|
BLAKE2b-256 checksum How to use checksums |
43ce8ca3cea5deda848c54c0407d9e2ecf9e72a688e2b0b75283db5a7d945f00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Security for jobagent 1.0.13
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.