Paper Writer
A LaTeX skeleton is a contract between the researcher and the typesetter. If the contract is broken the document does not compile, and the failure is loud. Build the skeleton first, then fill it.
Lesson 52 of 85Build~90 minutesPython
Before this: Lessons 48 to 51 of Phase 19
On this page
Learning Objectives
- Treat a research paper as a structured artifact with a known section graph, not a freeform document.
- Generate a LaTeX skeleton that declares its abstract, sections, figure slots, and bibliography keys before any prose is written.
- Inject figures from experiment outputs (paths and captions) into the skeleton through a deterministic slot mechanism.
- Wire a mocked prose generator that fills each section from a structured outline so the harness is testable without a model.
- Emit a single
paper.texplus areferences.bibplus a manifest that lists every figure referenced and every citation used.
ch-paper-skeleton
Why a skeleton first
A draft that starts as prose accumulates structural debt. The introduction grows three paragraphs that should be in related work. A figure gets referenced before it is defined. The bibliography ends up with three keys for the same paper. By the time the author notices, the rewriting cost is higher than the writing cost.
A skeleton inverts that. The structure is declared up front as data. Sections are slots with names and order. Figures are slots with ids and captions. Bibliography keys are declared at the top with the entries they point at. Prose is generated into those slots one at a time. The harness can validate, before any prose is written, that every figure has a slot, every citation has an entry, and every section appears in the table of contents.
This is the same discipline that earlier lessons applied to plans, tool calls, and traces. The structure is the contract.
The Paper shape
Every field is plain Python data. The renderer is a pure function from Paper to a LaTeX string. The harness can introspect the paper before rendering: count sections, list missing figure files, check that every \cite{key} has a matching BibEntry.
The render contract
The renderer guarantees three properties. First, every figure slot in the skeleton emits a \begin{figure} block with a stable label of the form fig:<id>. Second, every section emits a \section{} with a stable label of the form sec:<id> so cross-references work. Third, the bibliography emits a \bibliography block whose references.bib contains exactly the entries declared on the paper, no more and no fewer.
Violating any of these is a render error, not a warning. The skeleton is the contract; a render that silently drops a figure is a contract break.
Figure injection from experiments
The earlier lessons in this track produced experiment outputs as JSON manifests. Each manifest carries a list of artifacts with paths and short captions. The paper writer reads that manifest and produces Figure records.
The injection is deterministic. Figure ids are derived from the experiment name plus a monotonic counter. Captions come from the manifest. Paths are normalised relative to the paper's output directory so the LaTeX compiles even when the experiment outputs sit elsewhere on disk.
The mocked prose generator
The lesson does not call a model. A MockProseGenerator reads an outline shape and emits prose deterministically. The outline shape is one short string per section. The generator expands that string into two short paragraphs with the section title woven in. The generated prose name-drops figures and citations exactly when the outline declares them.
This is enough to test every behaviour of the writer. A real implementation would swap the generator for a model call. The harness around it does not change. That is the value of declaring the prose generator as a callable: the test substitutes a deterministic one, production substitutes a model one, the rest of the pipeline is identical.
The manifest output
The writer emits three files into the output directory.
The manifest is what a downstream evaluator or critic loop reads. It does not parse LaTeX; it reads the manifest. The next lesson, the critic loop, takes this manifest as input and produces a feedback list. That is why the manifest is part of the contract and the LaTeX is not.
Validation gates
The writer runs four gates before writing any file.
- Every figure id is unique within the paper.
- Every section's
citesfield references a bibliography key that is declared on the paper. - The abstract is non-empty.
- The title is non-empty.
A failed gate raises PaperValidationError with a precise reason. The harness surfaces the reason as the failure mode. There is no partial write: either all three files are emitted, or none.
How to read the code
code/main.py defines Paper, Section, Figure, BibEntry, PaperValidationError, MockProseGenerator, PaperWriter, and a render_latex function. The write method takes an output directory and emits paper.tex, references.bib, and manifest.json. The read_experiment_manifest helper converts a list of experiment manifests into Figure records.
code/tests/test_paper_writer.py covers: skeleton render with no sections, full render with two sections and two figures, missing-citation gate, duplicate-figure-id gate, manifest content, and the LaTeX-string contract (every section emits a \section{}, every figure emits a \begin{figure}).
Going further
Two extensions a real implementation will want. First, multi-format render: the same Paper shape compiles to Markdown for blog posts and HTML for previews. The renderer becomes a strategy on Paper. Second, citation enrichment: the writer fetches BibTeX entries from a citation key, given a local cache of DOIs. Both add value, both can be added without touching the skeleton contract.
The skeleton is the bet. Sections, figures, and citations declared as data, prose generated into slots, manifest emitted alongside the LaTeX. Every other improvement composes on top.
Check yourself
Quick quiz
Why does the writer declare sections, figures, and citations as data before any prose is written?
- Because BibTeX cannot resolve forward references
- Because LaTeX requires a forward declaration
- Because the model needs a system prompt
- Because the structure is the contract: the harness can validate completeness before prose generation
Show the answer
D. Because the structure is the contract: the harness can validate completeness before prose generation Structure-first lets the harness validate uniqueness, citation coverage, and figure coverage before invoking the prose generator. The skeleton is the contract.
What does the figure-injection step produce from an experiment manifest?
- A list of Figure records with stable ids and paths normalised to the paper directory
- A bibliography stub
- A PDF compiled from artifacts
- A model prompt with figure descriptions
Show the answer
A. A list of Figure records with stable ids and paths normalised to the paper directory read_experiment_manifest returns Figure records. Ids are deterministic, paths are normalised, captions come from the manifest.
Which validation failure raises PaperValidationError before any file is written?
- An abstract longer than 200 words
- An odd number of authors
- A section that cites a key not declared in the bibliography
- A figure path that does not exist on disk
Show the answer
C. A section that cites a key not declared in the bibliography Missing bibliography keys, duplicate figure ids, empty title, empty abstract, and unknown figure refs all raise PaperValidationError. Existence on disk is not enforced because the LaTeX compiler enforces it later.
Why is the prose generator a callable injected into PaperWriter rather than a method on it?
- Because tests can swap a deterministic MockProseGenerator for a model call without touching the writer
- Because LaTeX requires a callback
- Because the dataclass cannot hold state
- Because Python does not allow methods to take Paper as an argument
Show the answer
A. Because tests can swap a deterministic MockProseGenerator for a model call without touching the writer The writer takes the prose generator as a dependency. The test substitutes MockProseGenerator. Production substitutes a model call. The writer is unchanged.
What three files does PaperWriter.write emit on success?
- paper.tex, references.bib, manifest.json
- manifest.json, prose.txt, paper.pdf
- paper.tex, paper.pdf, references.bib
- outline.json, paper.tex, figures.tar
Show the answer
A. paper.tex, references.bib, manifest.json paper.tex is the LaTeX source. references.bib is the bibliography. manifest.json is the structured artifact the critic loop reads next.
Why is the manifest part of the writer's contract instead of just the LaTeX file?
- Because downstream tooling (critics, dashboards) reads structured manifests, not LaTeX
- Because manifest.json is the only file BibTeX accepts
- Because LaTeX is too large to parse
- Because LaTeX compilers reject untyped sections
Show the answer
A. Because downstream tooling (critics, dashboards) reads structured manifests, not LaTeX The manifest is a stable interface for downstream stages. Critics and dashboards inspect manifest.json. LaTeX is the human-readable artifact, not the machine interface.
Questions people ask
Can AI write research papers?
AI can draft sections, fill tables from results and format a paper. The science still has to be right, so people must check claims, numbers and citations. Templates help by fixing the structure the AI fills in.
Why use a template for AI-generated reports?
A template fixes the sections, layout and required fields. The AI only fills in content, so reports look the same each time and missing pieces are obvious. It also makes reports easier to compare and review.
Lessons from AI Engineering from Scratch by Rohit Ghumare and contributors, MIT license. Notes for owners by Lozo. License