Architecture — Overview¶
docsynth has two parts and a hard line between them.
- A document-agnostic kernel does all the mechanical work: dividing a run into units, coordinating workers, formatting money and dates, running the Jinja/ Chromium render, degrading captures, computing the golden set, writing the manifest, and exporting — none of which knows what an "invoice" is.
- A pack teaches the kernel how to produce one document type: its record
model, its templates, its label vocabulary, and the mapping from a record to a
render context. The first pack is
invoice.
Everything docsynth does is one of those two things collaborating across a small, explicit contract (Kernel ↔ pack contract). Testing the spine once and adding document types as packs — not forks — is the whole point.
The one-paragraph model¶
Content is built once, offline, into a versioned artifact; generation then draws from it locally with no API key. For invoices the content is procedural (companies, product lines, prices — all computed from a seed), so there is nothing to pre-build and a run is fully offline. For a text-heavy type (contracts, whose clauses are natural language) the content is generated by an LLM in a one-time catalogue step and published as a read-only artifact; the document run itself still makes no LLM calls — it reads the finished catalogue. That build-once- then-generate-locally split is the shape of the entire system.
The pieces¶
flowchart TB
subgraph Entry["Entry points"]
CLI["docsynth (CLI)"]
Studio["docsynth studio"]
Deploy["deploy.sh · Cloud Run Jobs"]
end
subgraph Kernel["Kernel — document-agnostic"]
direction TB
Pipeline["Pipeline<br/>plan · worker · source · renderer · pdf · degrade<br/>golden · manifest · export"]
Contracts["Contracts<br/>DocumentPack · GoldenRecord · ContentCapability"]
Cross["Cross-cutting<br/>locale · money · render/fonts · selection · logging"]
end
subgraph Pack["Pack — one document type"]
Invoice["invoice<br/>record · templates · sampler · catalogue · validation"]
end
subgraph Backends["Pluggable backends — chosen by URI scheme"]
Store["storage<br/>file · gs · s3"]
State["state<br/>sqlite · firestore · dynamodb"]
Sinks["golden sinks<br/>parquet · duckdb · bigquery"]
Prov["LLM providers<br/>OpenAI-compatible · Anthropic"]
end
CLI --> Kernel
Studio --> Kernel
Deploy --> Kernel
Kernel <--> Pack
Kernel --> Backends
- Entry points all drive the same kernel. The
docsynthCLI is the primitive (generate/export/catalogue/status/ …);docsynth studiois the interactive/scriptable orchestrator over it anddeploy.sh;deploy.shruns it as Cloud Run Jobs. See Studio and Operations. - Kernel holds the pipeline, the two contracts, and the cross-cutting services
(locale formatting, cent-exact money, the Jinja environment + bundled fonts,
the
Selectioncomposition vocabulary, structured logging). - Pack supplies the document type. It never names a backend or a provider — it says what content it needs and how a record renders.
- Backends are selected by URI scheme (
open_store/open_state/open_sink/ usage), sofile://→gs://,sqlite://→firestore://,duckdb://→bigquery://are config changes, not code changes.
How the principles show up here¶
The five core principles are visible in the shape above:
- ⑤ Extensible — the kernel/pack split is extensibility. A new document type is a new pack against the contract, no kernel fork; variants of a type live inside one pack.
- ③ Locally capable — the build-once-then-generate-locally model is the
local-first path. Whether content is key-free is a pack property
(
ContentCapability), declared through the contract, not a platform promise. - ② Provider-agnostic — the URI-scheme backends and the config-driven provider mix are the whole "swap by config, not code" story; nothing in the kernel or a pack hard-wires a model, a bucket, or a database.
- ④ High throughput — one run is divided into units worked by N tasks that coordinate through the state store's atomic claim, with no broker or leader (Concurrency & coordination).
- ① Realistic — but no PII — realism comes from the pack (coherent product lines, cent-exact math, degrade/handwriting); PII-freedom is structural (identifying details derived at generation, never stored) plus a screen on generated text.
Where to go next¶
- Kernel ↔ pack contract — the exact extension surface.
- Execution flows — run / catalogue-build / export sequences.
- Concurrency & coordination — units, the atomic claim, leases.
- Invoice pack → Overview — the whole flow made concrete.