AgenTeX: 14 skills + 2 hooks that keep LaTeX papers honest to the code behind them: verified citations with a cite guard, results-to-macros sync with…

<img src="docs/logo/agentex-logo.svg" alt="AgenTeX" width="440">
Keep your LaTeX paper honest to the code behind it.
Verified citations · numbers that can't go stale · a build that fixes itself · publication-grade TikZ · arXiv-ready packages
AgenTeX is a Claude Code plugin (Agent Skills plus hooks) for researchers whose results live in code and whose papers live in LaTeX.
Prose polishing is already well served by many tools. AgenTeX targets the problems where an agent with a shell, logs and APIs beats both chat assistants and editors, and it solves them with deterministic tools that make the final call, with the LLM organising the work and explaining the results.
fig3.pdf.(Each source was checked with AgenTeX's own cite_verify.py, and each quote against its abstract.)
| What you get | How | |
|---|---|---|
| 1 · Citations are real | Every cited entry is resolved against Crossref, arXiv, Semantic Scholar, OpenAlex and dblp, then classified as verified, mismatch ("Frankenstein" metadata), bad-doi, not-found (likely fabricated) or published (preprint has a published version). A cite-guard hook blocks the agent from adding any \cite whose entry isn't verified. | cite_verify.py + hooks/cite_guard.py |
| 2 · Numbers come from code | Results JSON → \res{exp.acc} macros. An audit finds stale numbers (equal to a previous value of a result), hard-coded numbers and undefined keys. Generated booktabs tables. A provenance lock records the script, input hashes and commit behind every figure, and rebuilds stale ones. | results.py |
| 3 · The build stays green | After a turn that edited LaTeX, a compile-loop hook builds the paper. If there are errors or new undefined references or citations, it hands the agent a short digest to fix before you ever see the broken PDF (it gives up after 3 attempts). | hooks/compile_loop.py + texlog.py |
Changes to references and numbers are never applied silently. Corrections are written as suggestions (.agentex/suggested.bib, audit replacements) for you to approve as a diff.
Tested TikZ patterns from latex-tikz, about 20 lines each on one shared style sheet. The agent copies the closest one, edits only what differs, then renders and looks at the result.
<table> <tr> <td align="center"><img src="docs/gallery/transformer.png" height="230"><br><sub>Transformer block</sub></td> <td align="center"><img src="docs/gallery/pgm.png" height="150"><br><sub>Graphical model with plates</sub></td> <td align="center"><img src="docs/gallery/plot.png" height="150"><br><sub>pgfplots with ±1 sd band</sub></td> </tr> <tr> <td align="center"><img src="docs/gallery/cnn.png" width="300"><br><sub>Network with tensor shapes</sub></td> <td align="center"><img src="docs/gallery/mlp.png" height="150"><br><sub>MLP from layer sizes</sub></td> <td align="center"><img src="docs/gallery/geometry.png" height="140"><br><sub>Exact geometric construction</sub></td> </tr> <tr> <td align="center"><img src="docs/gallery/pipeline.png" width="300"><br><sub>Pipeline with feedback loop</sub></td> <td align="center"><img src="docs/gallery/fsm.png" height="100"><br><sub>State machine</sub></td> <td align="center"><img src="docs/gallery/matrix.png" height="130"><br><sub>Matrix with dimension braces</sub></td> </tr> </table>
| Skill | What it does | |
|---|---|---|
| Verify | latex-references | Citation verification against five databases, BibTeX from DOIs, natbib/biblatex setup, label/citation linter |
latex-results-sync | Results registry → macros, stale/hard-coded number audit, generated tables, figure provenance and rebuilds | |
latex-consistency | Paper ↔ code audit: equations vs implementation, hyperparameters vs configs, overclaims, leakage red flags | |
latex-debug | Log digest (a 1,150-line log → 1 line), error lookup table, bisection workflow | |
| Write | latex-project | Paper/thesis scaffold next to your code; texmap.py project map so agents read only what they need |
latex-math | Code → equations via a symbol map, with shape/index checks against the implementation and notation audits | |
latex-tikz | Reads your code and suggests figures to pick from; 12 tested patterns, shared styles, offline search of 1,229 official TikZ manual examples, render-and-look loop | |
latex-algorithms | Functions → algorithmicx pseudocode, or minted/listings pulled from source files | |
latex-tables-figures | booktabs/siunitx tables, matplotlib/pgfplots figures sized to the column | |
latex-beamer | Paper → timed talk with claim-style titles and reused figures and notation | |
| Review | latex-research-check | Closest prior work across OpenAlex/arXiv/Semantic Scholar, ranked by similarity to your abstract |
latex-plagiarism-check | Sentence-level overlap with sources, with cited/quoted status (honest about what it can't do) | |
latex-review | Venue-style mock review, 3-reviewer panel + meta-review, anonymity checklist, rebuttal prep | |
| Ship | latex-arxiv | Only the files the document uses, comments stripped, arXiv's 2025 bibliography rules (.bbl format 3.3), test-compiled |
Claude Code plugin (skills + hooks), recommended. One command in your terminal:
claude plugin install agentex --marketplace Metahemeralism/AgenTeX
Or with the animated installer (it runs the same command):
curl -fsSL https://raw.githubusercontent.com/Metahemeralism/AgenTeX/main/install.sh | bash
Or from inside Claude Code:
/plugin marketplace add Metahemeralism/AgenTeX
/plugin install agentex
Restart Claude Code afterwards. To update: /plugin marketplace update agentex.
Skills only (no hooks): cp -r AgenTeX/skills/* ~/.claude/skills/. To get the guarantees without the plugin, copy the hooks block from hooks/hooks.json into your settings.json, replacing ${CLAUDE_PLUGIN_ROOT} with your clone's path.
Claude apps / other agents: each skill is a folder with a SKILL.md in the open Agent Skills format. Zip one and upload it under Settings → Capabilities → Skills, or point any agent at it.
Requirements: Python 3.8+ (all scripts are standard library only), and TeX Live / MacTeX or tectonic for compiling. Optional: poppler (pdftoppm, pdftotext) for previews and PDF sources, and S2_API_KEY (free) for reliable preprint→published detection.
| Setting | Effect |
|---|---|
AGENTEX_CITE_GUARD=0 | turn off the cite guard |
AGENTEX_COMPILE_HOOK=0 | turn off the compile loop |
S2_API_KEY=… | Semantic Scholar key (anonymous use is rate-limited) |
AGENTEX_MAILTO=you@uni.edu | joins Crossref's polite pool |
Caches and state live in .agentex/ in your project, which ignores itself in git.
The AgenTeX band. A small band above the prompt shows what AgenTeX is doing while its checks run (verifying citations, typesetting, rendering a figure). TeX's lowered E bobs as it works:
AgenT X ⠹ verifying citations 4s
E
It also plays a one-time welcome after install. Turn it off under /config → Animations. The band uses Claude Code's hooks-module API, which is marked early access (it may change between releases). It is purely cosmetic: the skills, cite guard and compile loop do not depend on it.
> Check every citation in paper/ and show me what's wrong.
> I reran the experiments. What in the paper is now stale?
> Make results/main.csv into a table with the best numbers in bold.
> Suggest diagrams for this repo. (reads the code, offers high-level and component figures to pick from)
> Draw the model in src/model.py as a TikZ figure with tensor shapes.
> Has anyone done this before? Find the closest work to my abstract.
> Review my paper as a NeurIPS reviewer.
> Package paper/ for arXiv.
| Script | Does | ||||
|---|---|---|---|---|---|
latex-references/scripts/cite_verify.py paper/ [--suggest] | Verify cited entries; exit 1 on fabricated, mismatched or duplicate entries | ||||
| `latex-results-sync/scripts/results.py build\ | audit\ | table\ | stale\ | make` | Registry, stale-number audit, tables, provenance |
latex-references/scripts/check_refs.py paper/ | Undefined/duplicate labels, missing/uncited bib entries | ||||
latex-debug/scripts/texlog.py main.log | Log digest | ||||
latex-project/scripts/texmap.py paper/ | Compact project map | ||||
latex-tikz/scripts/diagram_ideas.py . | Diagram ideas from your code (pipelines, architectures, loops, models), each with its pattern and file:line | ||||
latex-tikz/scripts/tikzref.py brace decoration | Offline TikZ manual search | ||||
latex-tikz/scripts/tikzcheck.sh fig.tex | Compile a figure; errors only + small PNG preview | ||||
latex-research-check/scripts/related.py --from-tex main.tex | Closest prior work | ||||
latex-plagiarism-check/scripts/overlap.py paper/ sources/ | Overlap screening | ||||
latex-arxiv/scripts/arxiv_pack.py main.tex --compile | arXiv package, test-compiled |
Lessons from auditing the ponytail and agent-skills plugins, applied here:
| Technique | In AgenTeX | Effect (measured on this repo) |
|---|---|---|
| Short always-loaded descriptions | ~250 characters per skill | bodies load only when used |
| Deterministic scripts instead of reading | texlog.py, cite_verify.py, results.py audit | a 1,150-line compile log → 1 line |
| Compact project map | texmap.py | template project mapped in 663 characters |
| Local reference cache | tikzref.py over extracted manual examples | 248 characters returned vs a 624 KB manual page |
| Result caches | .agentex/cite-cache.json | re-verifying a bibliography goes from ~20 s to near-instant |
| Write styles once; copy and edit | agentex-tikz.sty, patterns, templates | output tokens spent only on what differs |
| Cheap visual checks | 100 dpi PNG previews | a few hundred image tokens per look |
latexmk or tectonic and adds a build at the end of turns that edited LaTeX. Turn it off with AGENTEX_COMPILE_HOOK=0.From a ranked study of LaTeX bottlenecks for people who code:
/venue-convert and /camera-ready: class/bibliography-style swaps, page-limit report, anonymous ↔ final mode\columnwidth: one matplotlib style file, detection of font-distorting \includegraphics rescalespaper-ci: pinned TeX build + GitHub Actions running cite/results audits and attaching a latexdiff/rebuttal: response letter mapped to the actual latexdiff/git changes\DocumentMetadata), alt text drafted from the code and data behind each figure, veraPDF checks.claude-plugin/ plugin.json + marketplace.json
hooks/ hooks.json, cite_guard.py, compile_loop.py, ui.tsx + frames.ts (the band) and tests
types/ state contract for the band (TypeScript)
install.sh animated installer
skills/
latex-references/ cite_verify.py, check_refs.py
latex-results-sync/ results.py
latex-consistency/ latex-debug/ (texlog.py) latex-project/ (templates, texmap.py)
latex-math/ latex-tikz/ (assets, tikzref.py, tikzcheck.sh, manual index)
latex-algorithms/ latex-tables-figures/ latex-beamer/ (talk template)
latex-research-check/ latex-plagiarism-check/ latex-review/ (venue forms)
latex-arxiv/ (arxiv_pack.py)
docs/gallery/ rendered pattern previews
docs/logo/ wordmark source (.tex) + PDF/SVG/PNG, card and transparent variants
Issues and PRs are welcome, especially roadmap items, venue review forms, and error→fix pairs for latex-debug.
SKILL.md. Its description decides when it triggers, so say when to use it, in under ~300 characters.bash for d in hooks skills/*/scripts; do python3 -m unittest discover -s "$d" -q || exit 1; done claude plugin validate . && claude plugin test . # the band's module, in Claude Code's own test kit ``agentex-tikz.sty.claude --plugin-dir path/to/AgenTeX loads the plugin straight from disk for that session.MIT, except skills/latex-tikz/reference/tikz-manual.jsonl. That file contains examples extracted from the PGF/TikZ manual (© Till Tantau and the PGF/TikZ team) and is distributed under LPPL 1.3c. See its notice.
hooks/ui.tsx 101 lines1// AgenTeX's band above the prompt: a one-time welcome after install, and a small animation
2// while AgenTeX work runs (its scripts, and the compile check its Stop hook does).
3import { atom, read, update } from 'claude-code'
4import type { EngineInterface, Register } from 'claude-code'
5
6import { activityRows, labelFor, welcomeRows, WELCOME_FRAMES } from './frames'
7
8const activity = atom({ plugin: 'agentex', key: 'activity' } as const, null)
9const frame = atom({ plugin: 'agentex', key: 'frame' } as const, 0)
10const welcome = atom({ plugin: 'agentex', key: 'welcome' } as const, null)
11
12// Module variables reset on reload; what the band draws lives in $.state.
13let running = 0 // AgenTeX tasks in flight; the band shows while any is
14let ticker: { cancel: () => void } | null = null
15let welcomeChecked = false
16
17async function begin($: EngineInterface, label: string) {
18 running += 1
19 await update($, frame, () => 0)
20 await update($, activity, () => ({ label }))
21 ticker ??= $.clock.every(100, () => void update($, frame, n => n + 1))
22}
23
24async function end($: EngineInterface) {
25 running = Math.max(0, running - 1)
26 if (running > 0) return
27 ticker?.cancel()
28 ticker = null
29 await update($, activity, () => null)
30}
31
32async function maybeWelcome($: EngineInterface) {
33 if (welcomeChecked) return
34 welcomeChecked = true
35 if (await $.store.get('welcomed')) return
36 await $.store.set('welcomed', true) // first: an interrupted run must not replay forever
37 let n = 0
38 await update($, welcome, () => 0)
39 const t = $.clock.every(90, () => {
40 n += 1
41 void update($, welcome, () => (n >= WELCOME_FRAMES ? null : n))
42 if (n >= WELCOME_FRAMES) t.cancel()
43 })
44}
45
46export const register: Register = (on, options) => {
47 if (options.animations === false) return // switched off in /config: register nothing
48 // Installed mid-session, the module may join after session.start: the first prompt covers that.
49 // A UI hook must never block a prompt: on any failure, just continue.
50 on('session.start', async ($, e, next) => (await maybeWelcome($), next(e))).catch(($, e, next) => next(e))
51 on('prompt.submit', async ($, e, next) => (await maybeWelcome($), next(e))).catch(($, e, next) => next(e))
52
53 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
54 const label = labelFor(e.command ?? '')
55 if (!label) return next(e)
56 await begin($, label)
57 try {
58 return await next(e)
59 } finally {
60 await end($)
61 }
62 }).catch(($, e, next) => next(e))
63
64 // The compile loop is a command hook on Stop; animate only if it takes a noticeable time.
65 on('classic.Stop', async ($, e, next) => {
66 let shown = false
67 const t = $.clock.after(600, () => {
68 shown = true
69 void begin($, 'typesetting · checking the build')
70 })
71 try {
72 return await next(e)
73 } finally {
74 t.cancel()
75 if (shown) await end($)
76 }
77 }).catch(($, e, next) => next(e))
78
79 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
80 if (e.props.hasSurvey) return next(e)
81 const w = await read($, welcome)
82 const a = await read($, activity)
83 if (w === null && a === null) return next(e)
84 const rows = w !== null ? welcomeRows(w) : activityRows(await read($, frame), a!.label)
85 const { Box, Text } = $.ui.resolve(e)
86 return (
87 <Box flexDirection="column" key="agentex">
88 {rows.map((row, i) => (
89 <Text key={`r${i}`}>
90 {row.map((s, j) => (
91 <Text key={`s${j}`} color={s.color} bold={s.bold} dimColor={s.dim}>
92 {s.text}
93 </Text>
94 ))}
95 </Text>
96 ))}
97 </Box>
98 )
99 })
100}
101hooks/frames.ts 61 lines1// Pure frame logic for the AgenTeX band: no engine calls, so it is unit-testable.
2// The motif is TeX's lowered E: it drops into place on first run and bobs while work runs.
3
4export const BLUE = '#56B4E9' // "Agen", the logo's Okabe-Ito sky blue
5export const WELCOME_FRAMES = 48 // x 90 ms = about 4.3 s
6const SPIN = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
7
8export type Seg = { text: string; color?: string; bold?: boolean; dim?: boolean }
9export type Row = Seg[]
10
11// Which shell commands are AgenTeX work, and what to call them while they run.
12const LABELS: [RegExp, string][] = [
13 [/cite_verify\.py/, 'verifying citations'],
14 [/results\.py\s+(audit|stale)\b/, 'checking numbers against the code'],
15 [/results\.py\s+make\b/, 'rebuilding stale figures'],
16 [/results\.py/, 'syncing results'],
17 [/arxiv_pack\.py/, 'packaging for arXiv'],
18 [/tikzcheck\.sh/, 'rendering figure'],
19 [/diagram_ideas\.py/, 'reading your code for diagram ideas'],
20 [/related\.py/, 'searching prior work'],
21 [/overlap\.py/, 'screening for overlap'],
22 [/check_refs\.py/, 'checking references'],
23 [/texmap\.py/, 'mapping the project'],
24 [/\b(latexmk|tectonic|pdflatex|lualatex|xelatex)\b/, 'typesetting'],
25]
26
27export function labelFor(command: string): string | null {
28 for (const [re, label] of LABELS) if (re.test(command)) return label
29 return null
30}
31
32/** "AgenT X" over " E": `shown` letters of AgenTEX typed so far; E up or dropped. */
33export function logo(shown: number, eDown: boolean): [Row, Row] {
34 const has = (i: number) => shown > i
35 const e = has(5) ? 'E' : ' '
36 return [
37 [
38 { text: 'Agen'.slice(0, Math.min(shown, 4)).padEnd(4), color: BLUE, bold: true },
39 { text: (has(4) ? 'T' : ' ') + (eDown ? ' ' : e) + (has(6) ? 'X' : ' '), bold: true },
40 ],
41 [{ text: ' ' }, { text: eDown ? e : ' ', bold: true }, { text: ' ' }],
42 ]
43}
44
45export function welcomeRows(n: number): Row[] {
46 const [top, bottom] = logo(Math.min(n + 1, 7), n >= 9) // type it out, then drop the E
47 if (n < 11) return [top, bottom]
48 return [
49 [...top, { text: ' installed ✓', bold: true }],
50 [...bottom, { text: ' cite guard and compile loop on · try "suggest diagrams for this repo"', dim: true }],
51 ]
52}
53
54export function activityRows(frame: number, label: string): Row[] {
55 const [top, bottom] = logo(7, Math.floor(frame / 5) % 2 === 0) // E bobs every 0.5 s
56 return [
57 [...top, { text: ` ${SPIN[frame % SPIN.length]} ${label}`, color: BLUE }, { text: ` ${Math.floor(frame / 10)}s`, dim: true }],
58 bottom,
59 ]
60}
61types/index.d.ts 16 lines1// State the AgenTeX UI module keeps for the session (drawn by the band above the prompt).
2export type Activity = { label: string }
3
4declare module 'claude-code' {
5 interface PluginState {
6 agentex: {
7 /** AgenTeX work in flight (a script, the compile check), or null. */
8 activity: Activity | null
9 /** Animation tick while `activity` is set (100 ms each). */
10 frame: number
11 /** Welcome animation tick on first run, or null when it is not playing. */
12 welcome: number | null
13 }
14 }
15}
16