# How Cover Letters Are Drafted and Revised in the AI Job Search System

> Discover how the AI Job Search system drafts and revises cover letters through a four-phase workflow. Learn how immutable profiles and style guides create polished XeLaTeX PDFs.

- Repository: [Mads Lorentzen/ai-job-search](https://github.com/MadsLorentzen/ai-job-search)
- Tags: how-to-guide
- Published: 2026-08-29

---

**The AI Job Search system generates cover letters through a deterministic four-phase workflow—drafting, review, revision, and finalization—that produces a one-page XeLaTeX PDF grounded in immutable candidate profiles and writing style guides.**

The open-source **MadsLorentzen/ai-job-search** repository orchestrates this process via the `/apply` command, implementing a strict "draft-review-revise-compile" loop that ensures factual accuracy and stylistic consistency. Every cover letter is constructed from source-of-truth documents including the candidate profile, behavioral guidelines, and writing style constraints.

## The Four-Phase Cover Letter Workflow

The system executes a multi-step pipeline defined in [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md), progressing from initial LaTeX generation through rigorous validation to final PDF output.

### Phase 1: Drafting the Initial LaTeX Source

The drafter agent begins by ingesting three canonical source files: [`01-candidate-profile.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/01-candidate-profile.md), [`04-job-evaluation.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/04-job-evaluation.md), and [`03-writing-style.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/03-writing-style.md). It loads the active cover letter template defined in [`.claude/skills/job-application-assistant/06-cover-letter-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/06-cover-letter-templates.md), which defaults to the **XeLaTeX** class `cover.cls`.

Using the language of the job posting, the system generates a new file at `cover_letters/cover_<company>_<role>.tex`. The structure follows a strict template architecture:

- Salutation (e.g., `Dear Hiring Manager,`)
- Opening paragraph establishing role interest
- Bullet list of relevant achievements
- Closing paragraph
- Signature block

The full LaTeX source is held in memory without intermediate disk reads during drafting, ensuring atomic document construction as implemented in [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md).

### Phase 2: Content and Technical Review

A reviewer agent receives the draft **inline** alongside the original job posting and CV draft. The reviewer validates four critical dimensions:

- **Content relevance**: All posting requirements must be explicitly addressed
- **Factual grounding**: Dates, metrics, and achievements must match [`01-candidate-profile.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/01-candidate-profile.md), `cv/main_example.tex`, and [`CLAUDE.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CLAUDE.md)
- **Tone and style**: The letter must conform to [`02-behavioral-profile.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/02-behavioral-profile.md) and [`03-writing-style.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/03-writing-style.md) constraints (no em-dashes, no clichés, active voice)
- **Technical correctness**: LaTeX structural integrity, specifically avoiding the "known template pitfall" where itemized lists are incorrectly nested inside `\lettercontent{}` blocks as documented in [`06-cover-letter-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/06-cover-letter-templates.md)

The reviewer returns **Part A** (structured JSON edits) and **Part B** (narrative suggestions) according to the specification in [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md).

### Phase 3: Applying Structured and Narrative Edits

The drafter processes feedback through two distinct mechanisms:

**Part A Edits**: Applied deterministically using the `Edit` tool with exact string replacement. Each edit specifies `file`, `old_string`, `new_string`, and `reason` parameters.

**Part B Suggestions**: Applied using contextual judgment to:
- Insert missing keywords and company-specific angles
- Fix LaTeX structural errors (e.g., extracting bullet lists from `\lettercontent{}` and wrapping them in required `\fontspec[Path=OpenFonts/fonts/raleway/]{Raleway-Medium}` blocks)
- Enforce style rules regarding voice and punctuation

### Phase 4: Compilation and Visual Validation

After revision, the system compiles the document using **XeLaTeX**:

```bash
xelatex -interaction=nonstopmode cover_<company>_<role>.tex

```

The compilation loop repeats until the output satisfies three hard constraints defined in [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md):
1. **Single-page output** (verified via `pdfinfo` page count)
2. **Visible signature block** in the rendered PDF
3. **Font consistency**: Bullet lists must render in Raleway-Medium matching the body text

```bash

# Verify page count (must be exactly 1)

pdfinfo cover_AcmeCorp_DataEngineer.pdf | grep Pages

```

## Technical Implementation Details

The system relies on immutable source files to prevent hallucination. During drafting, the agent references [`06-cover-letter-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/06-cover-letter-templates.md) to avoid known LaTeX pitfalls, specifically the restriction that `\lettercontent{}` macros cannot contain `itemize` environments. Instead, bullet lists require explicit font specification:

```latex
{\raggedright\fontspec[Path=OpenFonts/fonts/raleway/]{
Raleway-Medium}\fontsize{11pt}{13pt}\selectfont
\begin{itemize}
  \item {Designed a real-time ETL system processing 10 TB/day.}
  \item {Reduced query latency by 40\% using materialised views.}
\end{itemize}\par}

```

The reviewer's **Part A** output follows a strict JSON schema for programmatic application:

```json
[
  {
    "file": "cover_letters/cover_AcmeCorp_DataEngineer.tex",
    "old_string": "Dear Hiring Manager,",
    "new_string": "Dear Dr. Smith,",
    "reason": "personalized salutation based on hiring manager research"
  }
]

```

## Key Source Files

| File Path | Function |
|-----------|----------|
| [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md) | Orchestrates the drafting, review, revision, and compilation workflow |
| [`.claude/skills/job-application-assistant/06-cover-letter-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/06-cover-letter-templates.md) | Defines the LaTeX template, XeLaTeX compile command, and known structural pitfalls |
| `cover_letters/cover.cls` | Custom LaTeX document class requiring XeLaTeX compilation |
| `cover_letters/cover_example.tex` | Reference implementation for structural guidance and CI smoke testing |
| [`.claude/skills/job-application-assistant/03-writing-style.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/03-writing-style.md) | Provides style constraints (active voice, prohibition of em-dashes) |
| [`.claude/skills/job-application-assistant/02-behavioral-profile.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/skills/job-application-assistant/02-behavioral-profile.md) | Governs tone and voice validation criteria |

## Finalization and Record Keeping

Once the PDF passes visual inspection—confirming single-page output and proper font rendering—the system optionally runs an ATS check on the CV (cover letters are rarely parsed by ATS systems) and records the application in `job_search_tracker.csv`. The finalized LaTeX source and compiled PDF are presented to the user for final review before submission, as specified in the workflow steps of [`.claude/commands/apply.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/.claude/commands/apply.md).

## Summary

- The **AI Job Search system** uses a deterministic four-phase workflow triggered by the `/apply` command to generate cover letters.
- **Drafting** pulls from immutable source files including [`01-candidate-profile.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/01-candidate-profile.md) and [`06-cover-letter-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/06-cover-letter-templates.md) to create `cover_letters/cover_<company>_<role>.tex`.
- **Review** validates content accuracy, factual grounding against source documents, and LaTeX technical correctness.
- **Revision** applies structured JSON edits (Part A) and contextual narrative improvements (Part B), fixing known template pitfalls like improper `itemize` nesting.
- **Compilation** requires XeLaTeX and enforces strict visual constraints: single-page output, visible signature blocks, and Raleway-Medium font consistency.
- The system guarantees factual grounding by prohibiting drafters from reading additional files during generation, ensuring all claims trace back to pre-validated source documents.

## Frequently Asked Questions

### What LaTeX engine does the AI Job Search system use for cover letters?

The system requires **XeLaTeX** specifically, as the `cover.cls` template uses system fonts (Raleway-Medium) via `fontspec` that are incompatible with standard pdfLaTeX. The compilation command `xelatex -interaction=nonstopmode cover_<company>_<role>.tex` is defined in [`06-cover-letter-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/06-cover-letter-templates.md) and executed during the revision phase until the output meets visual validation criteria.

### How does the system ensure factual accuracy in generated cover letters?

Factual grounding is enforced through **source document immutability**: the drafter reads only [`01-candidate-profile.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/01-candidate-profile.md), the master CV, and job evaluation files during initial drafting. The reviewer then cross-references every date, metric, and achievement against these canonical sources plus [`CLAUDE.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/CLAUDE.md). The drafter cannot perform additional file reads during generation, preventing hallucination of qualifications not present in the source materials.

### What is the "known template pitfall" mentioned in the cover letter workflow?

The pitfall involves incorrectly nesting `\begin{itemize}` environments inside the `\lettercontent{}` macro. The template in [`06-cover-letter-templates.md`](https://github.com/MadsLorentzen/ai-job-search/blob/main/06-cover-letter-templates.md) explicitly prohibits this structure because it breaks font inheritance. Bullet lists must instead be wrapped in explicit `\fontspec` blocks specifying the Raleway-Medium font path, placed outside `\lettercontent{}` macros but within the document flow. The reviewer flags this error during Phase 2, and the drafter corrects it during Phase 3.

### Can the system handle personalized salutations for specific hiring managers?

Yes. During the **Review** phase, if the candidate provides hiring manager research, the reviewer generates a **Part A** JSON edit targeting the salutation line. The edit specifies the exact `old_string` (e.g., `Dear Hiring Manager,`) and `new_string` (e.g., `Dear Dr. Smith,`), which the drafter applies deterministically using the `Edit` tool before recompiling the document.