From 46cbfc5967b4923a0951286d8304918a46415621 Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 24 Sep 2026 19:44:06 +0200 Subject: [PATCH] feat(document-to-markdown): read the formats Read cannot open, locally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read handles PDF, images and notebooks natively; it does not open .docx, .xlsx, .pptx, .epub, .msg or .zip. One uvx command does, with no install step and no service. Deliberately does NOT call docmark, which is the counter-intuitive part. Measured 2026-09-24 on the same .docx, tables included: local markitdown and the docmark service return byte-identical Markdown. Conversion of a text-bearing document involves no LLM at all — the client is used only to describe images. A network hop, a bearer token and an ingress for the same bytes is not a trade worth making, and docmark is unreachable from a session on koala anyway (ClusterIP, NetworkPolicy admits supervisor and dispatch only). The boundary is measured, not asserted. The light extras register 18 converters; docmark registers 22. The difference is exactly DocxWithOCR, PdfWithOCR, PptxWithOCR and XlsxWithOCR — the vision path, which is the one case that must go through docmark because its sovereignty gate is the estate's only audited route from an image to a model. Two hazards the skill names because both bite silently: - markitdown[all] pulls az-doc-intel and az-content-understanding, Azure cloud converters. The extras list avoids having them on the path. - A blank template and a scanned PDF with no text layer both convert to zero characters and exit 0. Check the byte count; an empty success is the failure mode, and a confident summary of an unread document is the worst output this skill could produce. Refs mathias/docmark#3 Co-Authored-By: Claude Opus 5 --- SKILLS_INDEX.md | 1 + skills/document-to-markdown/SKILL.md | 126 +++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 skills/document-to-markdown/SKILL.md diff --git a/SKILLS_INDEX.md b/SKILLS_INDEX.md index 96dcc7e..7c0d65d 100644 --- a/SKILLS_INDEX.md +++ b/SKILLS_INDEX.md @@ -17,6 +17,7 @@ the SKILL.md, not the table. | `cognitive-load` | Cognitive complexity analysis (CLI score) | Identifying hotspots, before refactoring | claude-code, library | | `debug` | Hypothesis-first debugging discipline | Any test failure or bug whose cause is not immediately obvious — or a bug report with no failure text, which this skill refuses rather than guesses at | claude-code, library | | `discovery-framing` | Problem-before-solution discovery/framing method | Framing/validating a problem before any solution; opportunity sizing | claude-code, library | +| `document-to-markdown` | Read .docx/.xlsx/.pptx/.epub/.msg and other formats the Read tool cannot open | You need the contents of a binary document — Word, Excel, PowerPoint, EPUB, Outlook .msg, a zip of documents — and Read returns nothing useful | claude-code, library | | `dream` | Reflective consolidation pass over an agent's memory directory | Pruning/merging memory files, rebuilding a memory index | claude-code, library, chat | | `experiment-spec` | Rigorous experiment spec — falsifiable hypothesis, quantitative acceptance, null-result protocol | Scoping a research/ML phase instead of a feature | claude-code, library | | `feature-spec` | Implementation-level feature spec (tighter than spec-driven-dev) | Scoping one feature inside an already-specced project | claude-code, library | diff --git a/skills/document-to-markdown/SKILL.md b/skills/document-to-markdown/SKILL.md new file mode 100644 index 0000000..55b48bd --- /dev/null +++ b/skills/document-to-markdown/SKILL.md @@ -0,0 +1,126 @@ +--- +name: document-to-markdown +surfaces: [claude-code, library] +summary: "Read .docx/.xlsx/.pptx/.epub/.msg and other formats the Read tool cannot open" +use_when: "You need the contents of a binary document — Word, Excel, PowerPoint, EPUB, Outlook .msg, a zip of documents — and Read returns nothing useful" +description: Convert a binary document to Markdown locally with markitdown. No network, no LLM, safe for confidential material. Says when to use the docmark service instead. +--- + +# Document → Markdown + +## Overview + +`Read` opens PDFs, images and notebooks natively. It does **not** open `.docx`, +`.xlsx`, `.pptx`, `.epub`, `.msg` or `.zip`. This skill converts those locally. + +One command, no install step, no service: + +```bash +uvx --from 'markitdown[docx,pdf,pptx,xls,xlsx,outlook]' markitdown FILE > /tmp/out.md +``` + +First run downloads 38 packages (~10s); afterwards it is cached and instant. + +## When to use this + +You have a file on disk whose contents you need. That is the whole trigger. + +## When NOT to use this — use `docmark` instead + +Three cases, and only three: + +1. **A scanned PDF or an image whose content is pixels, not text.** Extracting + that needs a vision model. The estate's only audited path from an image to a + model is `docmark`, whose `sovereignty.py` constructs an LLM client *only* + when `DOCMARK_VISION_MODEL` matches `koala/*` or `iguana/*`, and silently + degrades to metadata-only otherwise. `tests/test_sovereignty.py` is the + load-bearing guard. Do not reach for a cloud vision model directly; that is + the boundary the gate exists to hold. +2. **You are writing an in-cluster service** (something in `dispatch`, + `ingestion`, `cobalt-dingo`). Call `docmark` over MCP rather than bundling a + converter into each service. Its NetworkPolicy already admits `supervisor` + and `dispatch`. +3. **The file is larger than ~25 MiB** or you want the input/output caps + enforced centrally. + +For a text-bearing document, the service returns **byte-identical output** to +the local command — measured 2026-09-24 on the same `.docx`, tables included. +A network hop, a bearer token and an ingress for the same bytes is not a +trade worth making. + +## Confidential material + +**Conversion is local and involves no LLM.** `markitdown` uses an LLM client +only to describe images; with none configured it does not call anything. No +bytes leave the machine. + +That makes this safe for client material, unlike anything that routes through a +model. Two cautions: + +- **Do not install `markitdown[all]`.** It pulls `az-doc-intel` and + `az-content-understanding` — Azure cloud document-intelligence converters. + They need explicit endpoint configuration to activate, but the extras list + above avoids having them on the path at all. +- The output is a file like any other. If the source was confidential, so is + `/tmp/out.md` — and the usual rule applies: it must not reach an open-egress + sink (ADR-0014). + +## Verify the conversion, do not assume it + +The failure mode is **a successful exit with empty output**. A `.docx` template +with no body content converts to zero characters and exits 0; so does a scanned +PDF with no text layer. Both look like success. + +```bash +uvx --from 'markitdown[docx,pdf,pptx,xls,xlsx,outlook]' markitdown FILE > /tmp/out.md +wc -c /tmp/out.md +``` + +If the byte count is 0 or implausibly small for the document's size, the +converter found no text layer — that is case 1 above, and it needs `docmark` +with a vision model, not a retry. + +## What it converts + +18 converters with the extras above: + +``` +Audio, BingSerp, Csv, Docx, Epub, Html, Image, Ipynb, OutlookMsg, Pdf, +PlainText, Pptx, Rss, Wikipedia, Xls, Xlsx, YouTube, Zip +``` + +`docmark` registers 22 — the same set plus `DocxWithOCR`, `PdfWithOCR`, +`PptxWithOCR`, `XlsxWithOCR`. **Those four are the entire difference**, and +they are exactly the vision path from case 1. Everything else is identical code +running in a different place. + +## Reading the output + +Convert to a file and `Read` it, rather than piping into the transcript — +a 200-page document produces a lot of Markdown and most of it will not be +what you needed. Convert, then grep or read the section you want. + +## Provenance + +`docmark` ADR-0013 (`mathias/infra docs/decisions/`) owns the service; this +skill is the local path. The split was decided in `mathias/docmark#3` on +measurement, not preference: three of docmark's four plausible consumers are +better served without it, and the fourth — vision under a sovereignty gate — is +why it exists. + +## Escalation + +Local, read-only, single command. Nothing here is handed back mid-task. + +Stop and hand back when: + +- **The output is empty or near-empty.** Do not retry, and do not guess at the + contents. Say which file, what the byte count was, and that it needs a vision + path — then either use `docmark` with a local vision model, or ask. A + confident summary of a document you could not read is the worst possible + output of this skill. +- **The document is client material and the task would send its contents + anywhere** — a commit message, an issue, a PR body, a cloud model. That is + ADR-0014's refusal, and it is not this skill's call to make. +- **The file is encrypted or password-protected.** markitdown will fail; asking + for the password is a human's decision, not a retry.