A multi-agent system that optimizes resumes for specific job descriptions using LLM-powered analysis.
┌─────────────────────────────────────────────────────────────────────────────┐
│ RESUME OPTIMIZER FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
INPUTS
══════
┌──────────────┐ ┌──────────────────┐
│ resume.json │ │ job-description │
│ (your CV) │ │ .txt (JD) │
└──────┬───────┘ └────────┬─────────┘
│ │
└──────────┬──────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ GRAPH EXECUTION (3 LLM calls) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ JobAnalyzer │ │ ResumeWriter │ │ ATSScorer │ │
│ │ (Agent) │─────▶│ (Agent) │─────▶│ (Agent) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Extracts: │ │ Optimizes: │ │ Evaluates: │ │
│ │ - Tech stack │ │ - Title/label * │ │ - ATS score │ │
│ │ - Domains │ │ - Summary │ │ - Match % │ │
│ │ - Principles │ │ - Skills order │ │ - Suggestions │ │
│ │ │ │ - Highlights │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ Output: Markdown Output: Raw text Output: Markdown │
│ (used as-is) (needs extraction) (used as-is) │
│ │
│ * Title/label is used for output folder naming │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STRUCTURED EXTRACTION (1 LLM call) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ JobAnalyzer → Markdown saved as-is (no extra LLM call) │
│ ResumeWriter → structured_output() → ResumeSchema (complex, needs LLM) │
│ ATSScorer → Markdown saved as-is (no extra LLM call) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
OUTPUTS
═══════
┌───────────────────────────────────────┐
│ output/<position-slug>/ │
│ ├── resume.json (structured) │
│ └── ats_report.md (plain) │
└───────────────────────────────────────┘
| Agent | Role | Output Format |
|---|---|---|
| JobAnalyzer | Extract domains, tech stack, principles from JD | Markdown (saved as-is) |
| ResumeWriter | Update title to match position, rewrite summary, reorder skills, tailor highlights | Raw text → structured via LLM |
| ATSScorer | Evaluate resume-JD match, provide score and suggestions | Markdown (saved as-is) |
The system makes 4 LLM calls total:
- Graph execution (3 calls): JobAnalyzer → ResumeWriter → ATSScorer
- Structured extraction (1 call): Only ResumeWriter output needs LLM parsing
- JobAnalyzer: Markdown analysis → saved directly, no parsing needed
- ResumeWriter: Complex nested schema (ResumeSchema) → needs
structured_output()for reliability - ATSScorer: Markdown report → saved directly to file, no parsing needed
The output folder name is derived from basics.label in the optimized resume (the position title set by ResumeWriter):
- ResumeWriter sets
basics.labelto match the target position - This label is slugified:
"Senior Software Engineer"→senior-software-engineer - Output saved to:
output/senior-software-engineer/
pip install -r requirements.txt -r gradio_requirements.txtdocker compose buildCreate a .env file or set environment variables:
# Model configuration
STRANDS_MODEL_ID=qwen/qwen3-coder-30b
STRANDS_API_KEY=not-needed
STRANDS_BASE_URL=http://10.0.0.28:1234/v1
# Input paths (optional)
RESUME_PATH=data/resume.md
JOB_DESCRIPTION_PATH=data/job-description.txt- Place your resume in
data/resume.json(JSON Resume format) - Place the job description in
data/job-description.txt - Run:
python main.py
- Place your resume in
data/resume.json(JSON Resume format) - Run the Gradio UI:
python gradio_ui.py
- The UI will be available at http://localhost:7860
The Gradio interface allows you to:
- Select from available LM Studio models
- Paste job descriptions in a text box
- See processing logs in real-time
- Download the optimized resume when complete
Run the Gradio UI in a Docker container:
- Make sure LM Studio is running on your host machine with a model loaded
- Place your resume in
data/resume.json - Build and run:
docker compose up --build
- Access the UI at http://localhost:7860
The Docker setup:
- Automatically connects to LM Studio on your host via
host.docker.internal - Mounts
data/andoutput/directories for persistence - Runs in the background with
-dflag:docker compose up --build -d
Results are saved to output/<position-slug>/:
resume.json- Optimized resume in JSON formatats_report.md- ATS compatibility report in markdown
The folder name is derived from the job title (e.g., senior-software-engineer).
resume-agents/
├── main.py # CLI entry point
├── gradio_ui.py # Web UI entry point
├── optimizer_graph.py # Multi-agent graph definition
├── schemas.py # Pydantic models for structured output
├── Dockerfile # Container image definition
├── docker-compose.yml # Docker orchestration
├── data/
│ ├── resume.json # Input resume (JSON Resume format)
│ └── job-description.txt
└── output/
└── <position-slug>/
├── resume.json
└── ats_report.md
Based on JSON Resume format:
basics: name, label (job title), email, phone, summary, location, profileswork: company, position, dates, summary, highlights, keywordsskills: name, level, keywordseducation,languages, etc.
Both JobAnalyzer and ATSScorer output markdown saved directly to ats_report.md.