Most résumé tools start from a prompt. Dossier starts from your experiences.
Above: a one-page sample rendered from
templates/resume/standard.texusing a synthetic candidate. Source content lives in examples/sample_resume_content.json; the rendered.texis at examples/sample_resume.tex.
Dossier is an experience-first application material generator. Instead of regenerating your résumé from scratch every time you apply, Dossier keeps your experiences as structured, first-class records in a local SQLite store — your dossier — and each application is a retrieval + selection + rewriting pass against it. Bullets you rewrite for one application can be promoted back into the dossier, so the record sharpens with every run.
Dossier runs inside Claude Code. Claude is
the agent — it parses the brief, matches experiences, writes bullets. Python
is just a thin I/O layer over SQLite and file rendering. There are no LLM
calls from Python and no anthropic dependency.
CLAUDE.mdis the agent rulebook for Claude Code. If you're not using Claude Code, you can skip it — the README, DESIGN.md, and theagents/*.mdworkflow specs are enough on their own.
- Not a prompt-first generator. Your experience record is the source of truth, not the prompt. The same dossier serves every application.
- Not a hosted service. Everything runs locally. Your data lives in a
SQLite file on your own machine (
./dossier.dbby default). - Not an auto-apply bot. Dossier produces drafts. You review, edit, and submit.
┌────────────────────┐
│ dossier.db │
intake ─►│ experience table │◄── sync_back
(add one) │ profile table │ (rewrites → store)
└──────────┬─────────┘
│ read
▼
brief ──► matcher ──► match_report.json
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
resume prose qa
(.tex) (.docx) (answers.md)
V1 is English-only. Bilingual / CN support is on the V2 roadmap (DESIGN.md §13).
Each box is an independent agent — a workflow document under agents/
that Claude follows when you trigger it. Agents never chain automatically:
one user turn, one agent. You decide when to matcher, when to resume, when to
sync_back.
| Agent | Trigger | Produces |
|---|---|---|
intake |
"save this experience" | New row in the experience table |
matcher |
"match this JD" / "match this scholarship call" | match_report.json (selected experiences + document strategy) |
resume |
"write the resume" | resume_<lang>.tex via a LaTeX template |
resume_fix |
"fix this compile error" | Edits on an existing .tex or template |
prose |
"write a cover letter / SOP / motivation letter" | *.docx |
qa |
"answer the application form" | answers_<lang>.md |
sync_back |
"promote these bullets back to the dossier" | Updated bullets_en on source experiences |
Prerequisites
- Python 3.11+
- Claude Code CLI
- An Overleaf account (for compiling
.texoutput — no local LaTeX toolchain required)
Setup
# 1. Clone and install
git clone https://github.com/Jay-00234/dossier.git
cd dossier
python -m venv .venv && source .venv/bin/activate
pip install -e .
# 2. Open Claude Code in this directory
claudeThe first write (intake or profile) creates ./dossier.db automatically.
Override the path by setting DOSSIER_DB_PATH in .env if you want the
file somewhere else (e.g. inside a synced folder for backup).
Try it without committing your own data
If you'd rather see a full run before feeding Dossier your real
experiences, examples/ contains a synthetic experience
record and a job description. The examples/README.md walks through
loading them into a throwaway DB and running the matcher end-to-end.
First run
In the Claude Code conversation, drive the agents explicitly. Two equivalent trigger styles:
Natural language:
intake this experience:
<paste messy narrative — CN or EN, either is fine>
match this JD:
<paste job description>
run resume
run prose as cover letter
Slash commands (defined in .claude/commands/):
/intake
<paste messy narrative>
/matcher
<paste JD>
/resume
/prose cover letter
Each agent writes its artifacts to output/<slug>/, where <slug> is
<org>_<opportunity>_<YYYYMMDD>. Compile .tex on Overleaf; open .docx
anywhere.
dossier/
├── CLAUDE.md # Project instructions Claude reads on startup
├── agents/ # Workflow documents Claude follows
│ ├── intake.md
│ ├── matcher.md
│ ├── resume.md
│ ├── resume_fix.md
│ ├── prose.md
│ ├── qa.md
│ ├── sync_back.md
│ └── references/ # Style notes (e.g. cover_letter_patterns.md)
├── dossier.db # SQLite store — source of truth (gitignored)
├── matcher_config.yaml # Matcher weights / thresholds / rules (DESIGN.md §8.2)
├── src/
│ ├── cache.py # JSON-snapshot layer in front of SQLite
│ ├── storage/ # SQLite store + canonical dataclasses
│ │ ├── models.py # Experience / Profile / Evidence
│ │ ├── base.py # ExperienceStore / ProfileStore protocols
│ │ ├── sqlite_store.py
│ │ └── schema.sql
│ └── renderers/
│ ├── latex.py # Fill .tex templates from content JSON
│ ├── typst_render.py # Fill .typ templates (local `typst compile`)
│ └── docx.py # Build prose .docx from content JSON
├── scripts/ # Thin CLI wrappers around src/
│ ├── show_tags.py
│ ├── list_experiences.py
│ ├── get_experiences.py
│ ├── write_experience.py
│ ├── update_experience.py
│ ├── write_profile.py
│ ├── refresh_cache.py
│ ├── render_resume.py
│ └── render_cover_letter.py
├── templates/
│ └── resume/ # Parameterized templates
│ ├── standard.tex # default EN, pdflatex on Overleaf
│ ├── standard_dba.tex # DBA-application variant
│ └── standard.typ # Typst version of the default template
├── examples/ # Synthetic experience + JD pair for first-run dogfooding
├── output/ # Generated material, one folder per application (gitignored)
└── .cache/ # JSON snapshots exported from SQLite (gitignored)
Every script in scripts/ is a pure wrapper around the local SQLite store
or the filesystem: read tags, write an experience, list rows, render a
template. No LLM calls, no anthropic dependency. The intelligence —
parsing a brief, choosing which experiences to surface, writing bullets —
happens in the Claude Code conversation.
This split has two consequences worth knowing:
- The
agents/*.mdfiles are workflow instructions for Claude, not system prompts shipped to an API. You can edit them and Claude will pick up your changes in the next conversation. - Agents are decoupled by design. Re-running
matchernever triggersresume. Re-runningresumereads the existingmatch_report.jsonas-is and does not regenerate it. The user orchestrates; Claude does not.
The decoupling rules are spelled out in CLAUDE.md and are enforced by convention (Claude reads them on every startup).
Résumé templates live in templates/resume/ with %%MARKER%% placeholders
that src/renderers/latex.py substitutes. The LaTeX templates are
parameter-filled, not LLM-generated — Claude writes the content JSON, and
the renderer merges it into a pre-validated .tex shell. This keeps the
preamble and macros stable across runs.
The default template (standard.tex) is derived from Jake Gutierrez's
"Jake's Resume" style via sb2nov/resume
(MIT) with an intermediate adaptation by Jay. Full attribution
and the upstream license text live in
THIRD_PARTY_NOTICES.md.
TBD. Template variants (
standard.tex/standard_dba.tex/standard.typ), the Typst-vs-LaTeX render path, and language-format conventions are all still being designed. See the "TBD" section inCLAUDE.mdfor specifics.
- Runtime: Python 3.11+
- Storage: SQLite (stdlib
sqlite3) - Rendering:
python-docxfor prose, string-template substitution for LaTeX - Validation:
pydantic - Agent: Claude Code
Dossier is a local, open-source tool, not a hosted service.
- Your data stays with you. Experiences live in
./dossier.dbon your own machine. Nothing is uploaded to a third party except the Claude API calls Claude Code itself makes on your behalf. - AI-generated content needs review. Bullets, cover letters, and translations may contain inaccuracies or hallucinations. Always proofread before submitting to an employer, school, or funder.
- No guarantees. The authors are not responsible for application outcomes, admissions decisions, or employment results.
MIT. Third-party material bundled in this repository (résumé templates) is attributed in THIRD_PARTY_NOTICES.md.
