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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jobagent-1.0.9.tar.gz.
File metadata
- Download URL: jobagent-1.0.9.tar.gz
- Upload date:
- Size: 114.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c78aa460e2f2c6a50c24d18018b387a1815030fa17fa45d0d037a10796528e4
|
|
| MD5 |
7f21e45c3e1b464967b33d9041d62208
|
|
| BLAKE2b-256 |
30c9d45eb577084c962f1f2dea6821717cbab4f780ec9b1c269c2f2438f7a680
|
File details
Details for the file jobagent-1.0.9-py3-none-any.whl.
File metadata
- Download URL: jobagent-1.0.9-py3-none-any.whl
- Upload date:
- Size: 125.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99c690c6883cfb2cb7d322a6e96e3587b125c7751421fe5e0998772898a6573e
|
|
| MD5 |
16d0d2dbae513726b79591def5e4d942
|
|
| BLAKE2b-256 |
e040cef2223b36e13d113b21265d8302fee3dad6dcd191251a30158e67b96f30
|