xl3
The deterministic runtime for AI-generated Excel reports. An LLM writes the template, xl3 renders the workbook — same template, same data, same bytes, every time.
Status: alpha · XTL spec 0.1 (draft) · breaking changes possible until 1.0
xl3 is a small TypeScript engine that turns a pair of .xlsx files —
a template (the workflow contract) and raw data — into a
finished, formatted workbook. The template is itself an .xlsx, authored
in Excel with familiar formulas plus a tiny embedded expression
language (XTL) for the things that must be known before the workbook
is written: filters, groups, aggregates, filename patterns.
It's a good fit when the template is generated, edited, or reviewed by an LLM (Claude, GPT, Gemini, Cursor, Codex, …) and you need the execution layer to stay deterministic, inspectable, and verifiable — not "AI guessing at the output cells."
English · 한국어 · 日本語 · 简体中文 · 繁體中文 · Español · Website · Spec · LLM authoring guide · Implementations · Roadmap · Governance
The split: model writes, runtime renders
┌──────────────────────────┐ ┌──────────────────────────┐
│ LLM (Claude / GPT / │ │ xl3 │
│ Gemini / Cursor / …) │ │ (deterministic runtime) │
│ │ │ │
│ natural language │ │ template.xlsx │
│ + sample report ───► │ emits │ + raw.xlsx │
│ │ │ → result.xlsx │
│ "monthly settlement │ │ │
│ by region, with │ │ same inputs │
│ per-region subtotals" │ │ → same bytes, always │
└──────────────────────────┘ └──────────────────────────┘
creative, stochastic boring, reproducible
LLMs are good at drafting a report shape from a prompt and a sample.
They are bad at producing the same .xlsx twice, preserving cell styles,
or honoring "this column must always be SUM-aggregated." xl3 fills that
gap: the model emits an .xlsx template once; every subsequent render
is a pure function of (template, data, inputs).
This split is what docs/llm-template-authoring.md,
the 154-fixture conformance corpus, and the intentionally small XTL
surface are designed for.
Quick example
A template can contain ordinary Excel content, __config__, and xl3 expressions:
__config__ key | Value |
|---|---|
source_sheet | Raw |
source_table | 1 |
output_file_pattern | customer-renewal-report.xlsx |
| Cell | Template value |
|---|---|
| A5 | {{ [Account] }} |
| B5 | {{ [Region] }} |
| C5 | {{ [Renewal] }} |
| E5 | {{ IF([Renewal] > 10000, "Priority", "Standard") }} |
Given this data workbook:
| Account | Region | Renewal | Owner |
|---|---|---|---|
| Acme Logistics | Seoul | 18400 | Mina |
| Beta Works | Busan | 7200 | Joon |
xl3 renders:
| Account | Region | Renewal | Owner | Tier |
|---|---|---|---|---|
| Acme Logistics | Seoul | 18400 | Mina | Priority |
| Beta Works | Busan | 7200 | Joon | Standard |
…with the template's number formats, fills, borders, merged headers, and
footer rows preserved verbatim. The output is an .xlsx you can open in
Excel, Numbers, or Google Sheets without conversion.
See spec/ for the language draft and conformance/ for the implementation-neutral fixture corpus and runner protocol.