# interviewstreet/hiring-agent architecture

> Architecture diagram of the GitHub repository interviewstreet/hiring-agent, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-09-17). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/interviewstreet/hiring-agent
- Repository: https://github.com/interviewstreet/hiring-agent

## Overview

#### Purpose and entry points
Hiring Agent is a Python 3.11+ CLI pipeline that ranks resume PDFs for a selected role. Its main entry point is `score.py`, invoked with a PDF and required `--role`; it also scaffolds new role bundles via `--init-role`. The pipeline produces a structured evaluation with category scores, evidence, bonuses, and deductions.

Paths: `score.py`, `roles.py`, `roles/software_engineering_intern/role.json`

#### Orchestration and lifecycle
`score.py` coordinates the full workflow: parse PDF, optionally enrich from GitHub, evaluate against the role rubric, print a report, and—when development mode is enabled—cache intermediate data and append a role-specific CSV. `config.py` centralizes development-mode behavior and model/provider resolution.

Paths: `score.py`, `config.py`, `evaluator.py`

#### PDF ingestion and resume structuring
PDF content crosses an untrusted-document boundary before LLM use. `pymupdf_rag.py` extracts pages into Markdown-like text using PyMuPDF; `pdf.py` partitions content into resume sections and sends each section through prompt-driven LLM extraction. The assembled output is a typed JSON Resume-style representation, normalized from potentially loose model JSON.

Paths: `pymupdf_rag.py`, `pdf.py`, `models.py`, `transform.py`

#### Prompt and schema boundary
Extraction is controlled by Jinja templates per resume section, while typed models define expected LLM/provider interfaces and resume structures. This is the key boundary between probabilistic LLM responses and application data: templates constrain output, response cleanup occurs in the LLM layer, and transformation/schema logic makes it usable downstream.

Paths: `prompts/template_manager.py`, `prompts/templates/basics.jinja`, `prompts/templates/projects.jinja`, `llm_utils.py`, `models.py`

#### GitHub enrichment
If the parsed resume exposes a GitHub profile, the enrichment stage fetches profile and repository signals, classifies projects, and uses an LLM prompt to select exactly seven meaningful repositories subject to contribution criteria. GitHub is an optional external dependency; `GITHUB_TOKEN` improves API rate limits, and development mode caches fetched results.

Paths: `github.py`, `prompts/templates/github_project_selection.jinja`, `score.py`

#### Role-driven evaluation
Evaluation is not hardcoded to one rubric. `evaluator.py` loads the selected role bundle and asks the LLM to apply its criteria and fairness constraints. `role.json` drives categories, weight maxima, bonuses, deductions, score bounds, reporting fields, and CSV columns; role-specific Jinja files provide the scoring system message and criteria.

Paths: `evaluator.py`, `roles.py`, `roles/software_engineering_intern/role.json`, `roles/software_engineering_intern/criteria.jinja`

#### LLM providers and infrastructure
The application supports local Ollama and hosted Google Gemini via OpenAI-compatible endpoints behind a shared provider abstraction. `providers.json` maps models to provider base URLs, required API-key environment variables, and model parameters; `DEFAULT_MODEL` selects a configured model. Ollama runs locally (default model is `gemma4:latest`), while Gemini requires `GEMINI_API_KEY`.

Paths: `providers.json`, `config.py`, `models.py`, `llm_utils.py`

Main entry points: [`llm_utils.py`](https://github.com/interviewstreet/hiring-agent/blob/main/llm_utils.py) (LLM gateway), [`score.py`](https://github.com/interviewstreet/hiring-agent/blob/main/score.py) (score CLI), [`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py) (Resume parser), [`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py) (GitHub enricher)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_cli["CLI orchestration"]
  node_score["score CLI<br/>Python CLI<br/>[score.py]"]
  node_config["Runtime config<br/>configuration module<br/>[config.py]"]
end

subgraph group_ingestion["Resume ingestion"]
  node_pdf_extract["PDF text extraction<br/>PyMuPDF adapter<br/>[pymupdf_rag.py]"]
  node_pdf_parse["Resume parser<br/>section extraction pipeline<br/>[pdf.py]"]
  node_templates["Extraction templates<br/>Jinja prompts"]
  node_resume_prompts["Resume prompt examples<br/>Jinja templates<br/>[basics.jinja]"]
  node_transform["Response normalization<br/>schema transformation<br/>[transform.py]"]
end

subgraph group_enrichment["GitHub enrichment"]
  node_github["GitHub enricher<br/>GitHub API integration<br/>[github.py]"]
  node_github_selection["Repository selection prompt<br/>Jinja prompt"]
end

subgraph group_evaluation["Role evaluation"]
  node_roles["Role loader<br/>role bundle manager<br/>[roles.py]"]
  node_role_json["Intern role rubric<br/>role definition<br/>[role.json]"]
  node_criteria["Role scoring criteria<br/>Jinja prompt<br/>[criteria.jinja]"]
  node_evaluator["Rubric evaluator<br/>LLM evaluation pipeline<br/>[evaluator.py]"]
end

subgraph group_llm["LLM infrastructure"]
  node_models["Typed models<br/>schemas<br/>[models.py]"]
  node_llm_utils{{"LLM gateway<br/>provider abstraction<br/>[llm_utils.py]"}}
  node_providers["Provider registry<br/>model/provider mapping<br/>[providers.json]"]
  node_ollama(("Local Ollama<br/>LLM provider"))
  node_gemini(("Google Gemini<br/>hosted LLM provider"))
end

node_score -->|"loads runtime settings"| node_config
node_score -->|"PDF input"| node_pdf_extract
node_pdf_extract -->|"page text"| node_pdf_parse
node_pdf_parse -->|"selects prompts"| node_templates
node_templates -->|"loads section templates"| node_resume_prompts
node_pdf_parse -->|"extracts sections"| node_llm_utils
node_llm_utils -->|"cleaned model JSON"| node_transform
node_transform -->|"typed resume"| node_models
node_score -.->|"optional profile enrichment"| node_github
node_github -->|"repository selection"| node_github_selection
node_github -->|"classifies/selects projects"| node_llm_utils
node_score -->|"candidate data + role"| node_evaluator
node_evaluator -->|"loads selected bundle"| node_roles
node_roles -->|"rubric definition"| node_role_json
node_roles -->|"scoring prompt"| node_criteria
node_evaluator -->|"requests evaluation"| node_llm_utils
node_llm_utils -->|"uses response schemas"| node_models
node_config -->|"resolves model"| node_providers
node_providers -->|"endpoint configuration"| node_llm_utils
node_llm_utils -.->|"local requests"| node_ollama
node_llm_utils -.->|"hosted requests"| node_gemini

click node_score "https://github.com/interviewstreet/hiring-agent/blob/main/score.py"
click node_config "https://github.com/interviewstreet/hiring-agent/blob/main/config.py"
click node_pdf_extract "https://github.com/interviewstreet/hiring-agent/blob/main/pymupdf_rag.py"
click node_pdf_parse "https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py"
click node_templates "https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py"
click node_resume_prompts "https://github.com/interviewstreet/hiring-agent/blob/main/prompts/templates/basics.jinja"
click node_transform "https://github.com/interviewstreet/hiring-agent/blob/main/transform.py"
click node_models "https://github.com/interviewstreet/hiring-agent/blob/main/models.py"
click node_github "https://github.com/interviewstreet/hiring-agent/blob/main/github.py"
click node_github_selection "https://github.com/interviewstreet/hiring-agent/blob/main/prompts/templates/github_project_selection.jinja"
click node_roles "https://github.com/interviewstreet/hiring-agent/blob/main/roles.py"
click node_role_json "https://github.com/interviewstreet/hiring-agent/blob/main/roles/software_engineering_intern/role.json"
click node_criteria "https://github.com/interviewstreet/hiring-agent/blob/main/roles/software_engineering_intern/criteria.jinja"
click node_evaluator "https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py"
click node_llm_utils "https://github.com/interviewstreet/hiring-agent/blob/main/llm_utils.py"
click node_providers "https://github.com/interviewstreet/hiring-agent/blob/main/providers.json"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_score,node_config toneBlue
class node_pdf_extract,node_pdf_parse,node_templates,node_resume_prompts,node_transform toneAmber
class node_github,node_github_selection toneMint
class node_roles,node_role_json,node_criteria,node_evaluator toneRose
class node_models,node_llm_utils,node_providers,node_ollama,node_gemini toneIndigo
```

## Components

### CLI orchestration

- [score CLI](https://github.com/interviewstreet/hiring-agent/blob/main/score.py): Python CLI · Runs scoring or initializes role bundles. · `score.py`
- [Runtime config](https://github.com/interviewstreet/hiring-agent/blob/main/config.py): configuration module · Resolves development mode and selected model/provider. · `config.py`

### Resume ingestion

Untrusted PDF is converted into structured resume data.

- [PDF text extraction](https://github.com/interviewstreet/hiring-agent/blob/main/pymupdf_rag.py): PyMuPDF adapter · Converts PDF pages to Markdown-like text. · `pymupdf_rag.py`
- [Resume parser](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py): section extraction pipeline · Partitions resume content and invokes section extraction. · `pdf.py`
- [Extraction templates](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py): Jinja prompts · Constrain per-section resume extraction. · `prompts/template_manager.py`
- [Resume prompt examples](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/templates/basics.jinja): Jinja templates · `prompts/templates/basics.jinja`
- [Response normalization](https://github.com/interviewstreet/hiring-agent/blob/main/transform.py): schema transformation · Makes loose model JSON usable downstream. · `transform.py`

### GitHub enrichment

- [GitHub enricher](https://github.com/interviewstreet/hiring-agent/blob/main/github.py): GitHub API integration · Fetches profile and repository signals when a profile is present. · `github.py`
- [Repository selection prompt](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/templates/github_project_selection.jinja): Jinja prompt · Selects exactly seven meaningful repositories. · `prompts/templates/github_project_selection.jinja`

### Role evaluation

- [Role loader](https://github.com/interviewstreet/hiring-agent/blob/main/roles.py): role bundle manager · Loads selected role definitions and scaffolds bundles. · `roles.py`
- [Intern role rubric](https://github.com/interviewstreet/hiring-agent/blob/main/roles/software_engineering_intern/role.json): role definition · Defines weights, score bounds, bonuses, deductions, and report fields. · `roles/software_engineering_intern/role.json`
- [Role scoring criteria](https://github.com/interviewstreet/hiring-agent/blob/main/roles/software_engineering_intern/criteria.jinja): Jinja prompt · `roles/software_engineering_intern/criteria.jinja`
- [Rubric evaluator](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py): LLM evaluation pipeline · Applies role criteria and fairness constraints to the candidate. · `evaluator.py`

### LLM infrastructure

- [Typed models](https://github.com/interviewstreet/hiring-agent/blob/main/models.py): schemas · Defines resume data and provider-facing structures. · `models.py`
- [LLM gateway](https://github.com/interviewstreet/hiring-agent/blob/main/llm_utils.py): provider abstraction · Cleans responses and sends OpenAI-compatible requests. · `llm_utils.py`
- [Provider registry](https://github.com/interviewstreet/hiring-agent/blob/main/providers.json): model/provider mapping · Maps models to endpoints, API-key variables, and parameters. · `providers.json`
- **Local Ollama**: LLM provider · Default local model runtime.
- **Google Gemini**: hosted LLM provider · Hosted provider requiring GEMINI_API_KEY.

## Connections

- score CLI → Runtime config: loads runtime settings
- score CLI → PDF text extraction: PDF input
- PDF text extraction → Resume parser: page text
- Resume parser → Extraction templates: selects prompts
- Extraction templates → Resume prompt examples: loads section templates
- Resume parser → LLM gateway: extracts sections
- LLM gateway → Response normalization: cleaned model JSON
- Response normalization → Typed models: typed resume
- score CLI → GitHub enricher: optional profile enrichment
- GitHub enricher → Repository selection prompt: repository selection
- GitHub enricher → LLM gateway: classifies/selects projects
- score CLI → Rubric evaluator: candidate data + role
- Rubric evaluator → Role loader: loads selected bundle
- Role loader → Intern role rubric: rubric definition
- Role loader → Role scoring criteria: scoring prompt
- Rubric evaluator → LLM gateway: requests evaluation
- LLM gateway → Typed models: uses response schemas
- Runtime config → Provider registry: resolves model
- Provider registry → LLM gateway: endpoint configuration
- LLM gateway → Local Ollama: local requests
- LLM gateway → Google Gemini: hosted requests
