Picture in. PDF/A out. Capsule hashed. GPU-free.
Command::Convert · SHA-256 ×3 · i32 1/7200" · PNG/JPEG → PDF/A XObject · not OCR.
MinerU parses a scan. DocAgent embeds a PNG.
vs the field · Quickstart · What ships · API · Docs
Unique vs the locked ten — Command/Query/Event · capsule SHA-256 ×3 · prove same bytes. Not OCR. WeasyPrint tightness not claimed.
MinerU, Docling, Pandoc, Marker, Unstructured, PyMuPDF, WeasyPrint, ONLYOFFICE, python-docx, and LibreOffice do not ship an agent API, a three-hash capsule, and byte-identical replay together.
- Picture in, PDF/A out. PNG/JPEG on Markdown, HTML, DOCX, ODT paint as a PDF/A XObject. We embed the bytes. We do not OCR.
- Integer layout. Coordinates are
i32at 1/7200 inch. Same fonts → same bytes. GPU-free. - Hangul is a regional codec on the same IR (images —), not the product name.
- Not claimed: OCR, VLM, OmniDocBench, WeasyPrint print-CSS tightness (table/paragraph still open).
Hashes below are the SHA-256 ×3 in letter-capsule.json.
examples/letter.md → PDF/A + HTML + capsule. Teal harbor mark under the H2. JSON b468b37f…; trio/capsule panel still b867db0a… (shots lag — not a fake hash). WeasyPrint tightness not claimed closed.
| Hash | SHA-256 (letter-capsule.json) |
|---|---|
| input | b0f4423728db4238779b3bf6cbd238d4d0d637f733c078322be6ff23398bbcdf |
| plan | 4b7360eb1b7a9bec20cc35d5c79f2156c22d2712ab62ededfb60e328b8f16d3a |
| output | b468b37f1c3d8da7e4aac7e1a8417b46c29a57863817f9df16e8d4a6cc89f0f8 |
Locked against the ten most-starred document repositories an agent actually meets (star counts 2026-09-06). Agent-runtime matrix, not an OCR bake-off. No OmniDocBench numbers.
| Tool | Job | C/Q/E | Capsule | Same bytes | i32 layout | GPU-free | Hangul |
|---|---|---|---|---|---|---|---|
| DocAgent | Agent document runtime | yes | SHA-256 ×3 | yes | 1/7200" | yes | yes |
| MinerU 79k | PDF/Office → LLM markdown | ||||||
| Docling 66k | Gen-AI parse + export | optional | |||||
| Pandoc 46k | Universal markup convert | yes | |||||
| Marker 40k | PDF → markdown/JSON | ||||||
| Unstructured 15k | Document ETL for LLMs | ||||||
| PyMuPDF 11k | PDF extract/convert | yes | |||||
| WeasyPrint 10k | HTML/CSS → PDF | yes | |||||
| ONLYOFFICE 6.9k | Collaborative office suite | yes | |||||
| python-docx 5.7k | Word DOCX read/write | yes | |||||
| LibreOffice 4.3k | Headless office convert | yes |
North star: drop examples/letter.md (or DOCX/ODT) that contains a PNG or JPEG, and get PDF/A + HTML + Markdown + DOCX + ODT whose bytes match the next run, with three hashes in the capsule. The picture is a PDF/A XObject. Not OCR.
Images: yes PNG + JPEG on Markdown / HTML / PDF/A / DOCX / ODT. — on Hangul.
The demo letter is . First-screen convert shot above shows the teal mark under the H2. Demo-image gap closed. Print-CSS tightness vs WeasyPrint is not claimed closed.
git clone https://github.com/kevin9327/docagent
cd docagent
cargo test --workspace
cargo run -p docagent-cli -- convert examples/letter.md \
--pdf out.pdf --html out.html --odt out.odt --docx out.docx --capsule cap.json
cargo run -p docagent-cli -- prove examples/letter.mdexamples/letter.md contains . Convert paints that teal mark under the H2 as a PDF/A XObject. prove converts twice (PDF/A + HTML) and exits 0 only when the bytes and the three SHA-256 hashes match. The prove JSON (letter-prove.json) is a separate artifact from the convert capsule in the table above — those plan/output hashes stay distinct (928b4745… / 2607bb6f…). Do not copy them onto letter-capsule.json.
Binaries: docagent (CLI) · docagentd (daemon) · MCP + WIT adapters over the same Engine.
use docagent_api::{Command, Engine, ExportTarget};
let mut engine = Engine::new();
let out = engine.execute(Command::Convert {
input: std::fs::read("examples/letter.md")?,
input_kind: None,
targets: vec![ExportTarget::PdfA, ExportTarget::Html, ExportTarget::Markdown],
})?;
// out.capsule.{input_hash, plan_hash, output_hash}MinerU, Docling, Marker, and Unstructured turn documents into tokens for models. Pandoc, python-docx, WeasyPrint, ONLYOFFICE, and LibreOffice turn documents into other documents. DocAgent is the runtime in the middle that an agent can call, replay, and prove.
Honest codec coverage — not a wishlist. Full table: docs/src/capabilities.md.
| Mark | Markdown | HTML | PDF/A | DOCX | ODT | Hangul |
|---|---|---|---|---|---|---|
| Lists (ul/ol, nested) | yes | read ul/ol/tasks |
markers | w:numPr |
text:list |
yes |
| Links | [text](url) |
<a href> |
URI /Link |
w:hyperlink |
text:a |
yes |
| Strike | ~~text~~ |
<s> |
fill | w:strike |
line-through | yes |
| Quotes | > |
<blockquote> |
quote bar | Quote | Quote | yes |
| Code | `span` / fence |
<code> / <pre> |
fills | Code | Tcode | yes |
| Thematic break | --- |
<hr/> |
rule fill | HorizontalLine | HorizontalLine | yes |
| Tasks | - [x] |
checkbox | task box | [x] |
[x] |
yes |
| Highlight | ==mark== |
<mark> |
fill | w:highlight |
Tmark | yes |
| Super / sub | ^ ^ / ~ ~ |
<sup> / <sub> |
baseline | w:vertAlign |
Tsuper / Tsub | yes |
| Underline | __text__ |
<u> |
fill | w:u |
Tunder | yes |
| Images (PNG, JPEG) |  |
<img> |
XObject | a:blip |
draw:image |
— |
| Table header | GFM | --- | |
<th> / <thead> |
header fill | w:tblHeader |
THcell |
yes |
Images are the first-class row. PNG and JPEG round-trip on Markdown, HTML, PDF/A, DOCX, and ODT. They stay — on Hangul. That dash is real.
HTML read recovers data-URI <img>, lists, <th>/<thead>, <blockquote>, <pre>, <a href>, and <mark> into the same IR. http(s): src is skipped. This is not a full HTML5 engine.
No spreadsheet. No slides. No cursor. No undo stack. No OCR. Flow documents only.
Files from examples/letter.md: PDF/A · HTML · DOCX · ODT · Markdown · capsule JSON.
|
PDF/A from the integer layout engine. Teal harbor mark under Delivery confirmation — order 48291. GPU-free. Same fonts → same bytes. |
HTML from the same IR. Teal harbor mark under the H2 as |
Capsule from examples/letter.md: input / plan / output SHA-256. JSON b468b37f…; shot still shows b867db0a… (shots lag — not a fake hash). Two converts, same fonts, same bytes. GPU-free. Deterministic. Not OCR.
The test is letter_md_is_byte_identical_across_two_runs. Hashes above are the hashes in letter-capsule.json.
| Surface | What it is |
|---|---|
| IR | Format-blind document model. Layout never sees “docx” or “hwp”. |
| Layout | One engine. Coordinates are i32 at 1/7200 inch. f32 is forbidden. |
| PDF/A | Archival print from the integer plan. PNG and JPEG are XObjects. Two runs, same fonts, same bytes. |
| DOCX / ODT | Word and LibreOffice files. Headings, lists, links, tables, PNG + JPEG (a:blip / draw:image). |
| Markdown / HTML | Agent-native text and semantic HTML. . HTML <img> write / data-URI read. Not HTML5. |
| Capsule | Three SHA-256 hashes on every Command so a retry is evidence, not hope. |
| Hangul | Regional codecs on the same IR, not the product name. Images on Hangul are still —. |
Command → Convert | Import | Export | LayoutDocument
Query → EngineVersion | LastCapsule | LastDocument | CapsuleByInputHash
Event → CommandCompleted { capsule_id } | Warning
Every Command writes a capsule. CLI, daemon, MCP, and WIT are thin adapters over
the same Engine. See AGENTS.md and docs/src/intro.md.
flowchart LR
A[DOCX / ODT / MD / HTML + PNG/JPEG] --> B[docagent-model IR]
B --> C[docagent-layout<br/>i32 @ 1/7200 in]
C --> D[PDF/A XObject]
C --> E[HTML]
C --> F[DOCX / ODT / MD]
B --> G[capsule.json<br/>input · plan · output]
Codecs (docx, odt, md, html, hwp5, hwpx, hwp3, hml) do not import each other
and do not import layout. Layout, paint, raster, and PDF do not know the source format.
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warnings
cargo run -p docagent-lint
cargo run -p docagent-conformance -- --require-win
- Intro
- What ships
- Format hops
- ADRs — crate boundaries, integer layout, single engine, capsules
MIT. See LICENSE.




