You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Extend document ingestion beyond PDF so a .docx, .pptx, or .csv dropped into a project gets the same sidecar Markdown note a PDF gets today (report.docx.md next to report.docx), using microsoft/markitdown for the Office formats and the stdlib for CSV. PDF stays on pdf-inspector.
Evaluation (2026-09-07, markitdown 0.1.7)
PDF head-to-head on an 87-page two-column paper:
pdf-inspector 1.17
markitdown 0.1.7
Time
0.36s
5.55s
Page markers
87
0
Title
extracted
none
Text
clean headings, spaced words
words glued together, title mangled into a fake table
markitdown's PDF path is pdfminer plus a word-position table heuristic. It has no OCR detection, no confidence, and no page markers, so the page citations from #1490 would have nothing to anchor to. PDF stays on pdf-inspector.
Real files (24 from a personal iCloud vault, structure only):
Type
Result
docx
Good for letters and prose. Form-style layout tables collapse into stray pipes.
pptx
Good. <!-- Slide number: N --> markers, speaker notes, tables. Images become dangling  references.
xlsx / xls
Noisy. Title rows produce Unnamed: N headers and NaN in every empty cell (pandas header=0). Deferred.
csv
Good tables. One UTF-8 file came out as mojibake from markitdown's charset guess. Do it ourselves.
html
Good, title extracted. Not in scope yet.
json
Crashes: magika sniffs the prefix as ASCII, then IpynbConverter.accepts() decodes the whole stream and raises UnicodeDecodeError, which escapes the conversion loop.
rtf
Falls through to PlainTextConverter and dumps raw RTF markup.
odt, doc, pages, numbers, key
Unsupported.
Design
Dispatch on the media type we already index and call the specific markitdown converter directly. Never use the sniffing MarkItDown.convert() loop. That avoids magika, the requests.Session, ZipConverter recursion, the URL converters, and the json/rtf/charset failures above.
Extractor protocol.RawPdfDocumentRuntime.extractor is typed as the concrete PdfInspector. Introduce a neutral extraction result and a DocumentExtractor protocol keyed by media type; pdf-inspector becomes one implementation, markitdown (docx, pptx) another, CSV a stdlib one.
Bounded worker. Run markitdown in the same rlimit-capped, byte-capped, killable subprocess pattern as pdf_inspector_worker.py. markitdown is synchronous, in-process, and unbounded; its README tells hosted users to sanitize inputs themselves.
Run identity. Engine microsoft/markitdown plus the installed version from importlib.metadata, one profile per converter. Upgrades yield new runs rather than rewriting provenance, same as pdf-inspector. CSV gets its own engine so a markitdown upgrade does not re-run every CSV.
CSV. Decode as UTF-8 and fail fast. Header plus the first N rows plus a row count; a 475 KB export became a 7,400-row table with no semantic value.
Extra.basic-memory[documents] = markitdown[docx,pptx]. New install weight is roughly 40 to 50 MB (lxml, Pillow, mammoth, python-pptx). onnxruntime and numpy are already present via fastembed, so magika adds only its 4 MB model. No pandas.
Local entry point.bm import document <path> over the project directory: resolve the indexed source entity, extract, build artifacts through the existing contract, write <file>.<ext>.md and the document-ingestion-runs/<run-id>.md note, index them. This is the same local source-reader and writer work still pending for PDF from Parse PDFs into searchable LiteParse sidecar notes #1006.
Already neutral: derive_document_note_path yields x.docx.md; DocumentSourceV1.media_type accepts any MIME type; DocumentMetadataV1.kind is free text; run identity hashes engine, version, profile, and options.
To generalize: the hardcoded ("document", "pdf", "generated") tags and kind="pdf"; the pdf-specific pipeline and profile constants; DocumentExtractionV1 requires page fields (pageless formats pass with page_count=0, which should be documented or made optional); cloud's application/pdf gate becomes the dispatch table. Page citations stay PDF-only by design.
Cloud imports RawPdfDocumentRuntime, build_raw_document_artifacts, and the identity helpers, so the PDF signatures stay intact.
Known rough edges to accept for now
pptx image references point at internal picture names; strip to alt text.
docx form-style tables lose structure.
xlsx waits on a header strategy for title rows.
POC scope
Neutral extraction result + DocumentExtractor protocol; pdf-inspector adapted, existing tests green
markitdown bounded worker for docx and pptx behind basic-memory[documents]
stdlib CSV extractor with row cap
bm import document <path> local runtime (source reader, sidecar writer, run note)
Tests for mapping, CSV rendering, and worker dispatch
Summary
Extend document ingestion beyond PDF so a
.docx,.pptx, or.csvdropped into a project gets the same sidecar Markdown note a PDF gets today (report.docx.mdnext toreport.docx), using microsoft/markitdown for the Office formats and the stdlib for CSV. PDF stays on pdf-inspector.Evaluation (2026-09-07, markitdown 0.1.7)
PDF head-to-head on an 87-page two-column paper:
markitdown's PDF path is pdfminer plus a word-position table heuristic. It has no OCR detection, no confidence, and no page markers, so the page citations from #1490 would have nothing to anchor to. PDF stays on pdf-inspector.
Real files (24 from a personal iCloud vault, structure only):
<!-- Slide number: N -->markers, speaker notes, tables. Images become danglingreferences.Unnamed: Nheaders andNaNin every empty cell (pandasheader=0). Deferred.IpynbConverter.accepts()decodes the whole stream and raisesUnicodeDecodeError, which escapes the conversion loop.PlainTextConverterand dumps raw RTF markup.Design
Dispatch on the media type we already index and call the specific markitdown converter directly. Never use the sniffing
MarkItDown.convert()loop. That avoids magika, therequests.Session,ZipConverterrecursion, the URL converters, and the json/rtf/charset failures above.RawPdfDocumentRuntime.extractoris typed as the concretePdfInspector. Introduce a neutral extraction result and aDocumentExtractorprotocol keyed by media type; pdf-inspector becomes one implementation, markitdown (docx, pptx) another, CSV a stdlib one.pdf_inspector_worker.py. markitdown is synchronous, in-process, and unbounded; its README tells hosted users to sanitize inputs themselves.microsoft/markitdownplus the installed version fromimportlib.metadata, one profile per converter. Upgrades yield new runs rather than rewriting provenance, same as pdf-inspector. CSV gets its own engine so a markitdown upgrade does not re-run every CSV.basic-memory[documents]=markitdown[docx,pptx]. New install weight is roughly 40 to 50 MB (lxml, Pillow, mammoth, python-pptx). onnxruntime and numpy are already present via fastembed, so magika adds only its 4 MB model. No pandas.bm import document <path>over the project directory: resolve the indexed source entity, extract, build artifacts through the existing contract, write<file>.<ext>.mdand thedocument-ingestion-runs/<run-id>.mdnote, index them. This is the same local source-reader and writer work still pending for PDF from Parse PDFs into searchable LiteParse sidecar notes #1006.Contract touch points (
schemas/document.py,document_ingestion/raw_document.py)Already neutral:
derive_document_note_pathyieldsx.docx.md;DocumentSourceV1.media_typeaccepts any MIME type;DocumentMetadataV1.kindis free text; run identity hashes engine, version, profile, and options.To generalize: the hardcoded
("document", "pdf", "generated")tags andkind="pdf"; the pdf-specific pipeline and profile constants;DocumentExtractionV1requires page fields (pageless formats pass withpage_count=0, which should be documented or made optional); cloud'sapplication/pdfgate becomes the dispatch table. Page citations stay PDF-only by design.Cloud imports
RawPdfDocumentRuntime,build_raw_document_artifacts, and the identity helpers, so the PDF signatures stay intact.Known rough edges to accept for now
POC scope
DocumentExtractorprotocol; pdf-inspector adapted, existing tests greenmarkitdownbounded worker for docx and pptx behindbasic-memory[documents]bm import document <path>local runtime (source reader, sidecar writer, run note)