Hand-written docs and the generated per-skill mirror pages now show the same scratch locations the skills and prompts do (~/.hermes/cache/scratch, $TMPDIR, $HOME/.hermes/cache/scratch/<throwaway-home>) instead of /tmp, and examples that only needed a placeholder use /path/to/... The mirror pages were updated in place rather than regenerated: regenerating from the current sources produces a 200-file unrelated diff (Windows backslash paths, removed skills). Literals that describe /tmp itself stay and carry a no-tmp marker: the sandbox tmpfs configuration, the disk-cleanup plugin's scope, the WSL feature list, the terminal.temp_dir rationale, the Nix container's writable layer and the Docker Compose in-container pulse-cookie path. One tree-listing line in nix-setup.md stays unmarked (a marker would render inside the code block).
66 lines
5.0 KiB
Markdown
66 lines
5.0 KiB
Markdown
---
|
|
sidebar_position: 3
|
|
title: "Document Extraction"
|
|
description: "How read_file converts PDFs, Office documents, and notebooks to text — and what to do when a PDF is scanned images"
|
|
---
|
|
|
|
# Document Extraction
|
|
|
|
The `read_file` tool automatically converts common document formats to readable text, so the agent can inspect a PDF or spreadsheet the same way it reads source code.
|
|
|
|
## Supported formats
|
|
|
|
| Format | Extensions | Converter | Availability |
|
|
|--------|-----------|-----------|--------------|
|
|
| Jupyter notebooks | `.ipynb` | Built-in (stdlib) | Always |
|
|
| Word documents | `.docx` | Built-in (stdlib) | Always |
|
|
| Excel workbooks | `.xlsx` | Built-in (stdlib) | Always |
|
|
| SQLite databases | `.db`, `.sqlite`, `.sqlite3` | Built-in (stdlib) | Always |
|
|
| PDF | `.pdf` | Optional `anydoc` converter | Auto-installed on first use* |
|
|
| Legacy Office | `.doc`, `.ppt`, `.xls`, `.pptx`, and variants | Optional `anydoc` converter | Auto-installed on first use* |
|
|
| OpenDocument | `.odt`, `.ods`, `.odp` | Optional `anydoc` converter | Auto-installed on first use* |
|
|
| Rich text / eBooks | `.rtf`, `.epub` | Optional `anydoc` converter | Auto-installed on first use* |
|
|
|
|
\* The optional converter is the `firecrawl-anydoc` package, installed lazily where installs are permitted (`security.allow_lazy_installs` in `config.yaml`). Without it, the three stdlib formats still work; other formats fall back to the binary-file guard.
|
|
|
|
SQLite files render as a schema overview rather than a dump: each table's `CREATE` statement, row count and first five rows, plus the list of indexes, views and triggers. The database is opened read-only (`mode=ro&immutable=1`, so a live database another process holds open is still readable without locks); for anything beyond the preview, query it from the terminal with `sqlite3`. A `.db` that is not SQLite (the magic bytes do not match) is refused with the real reason. Hermes's own read denylist applies before extraction, so protected stores under `HERMES_HOME` stay unreadable.
|
|
|
|
`read_file` also flags unresolved git merge conflicts: when the returned range contains balanced `<<<<<<< ` / `>>>>>>> ` marker lines, the result carries `conflict_blocks: N` and a hint to resolve them before editing around them. A lone marker inside a string or a test fixture is not counted.
|
|
|
|
Conversion output is Markdown, paginated through `read_file`'s normal `offset`/`limit` window. East Asian phonetic guides (XLSX `rPh`, DOCX ruby text) annotate cell or run text and are not part of the extracted value: a cell holding 東京 with the guide トウキョウ reads as `東京`. Documents over 50 MB are refused to keep tool turns bounded.
|
|
|
|
Notebook cell outputs longer than 20,000 characters are truncated; the truncation marker carries a `jq` command that names the notebook's full, shell-quoted path so the omitted output can be pulled from the original file.
|
|
|
|
Extraction works with remote terminal backends (Docker, Modal, SSH): the file's bytes are transferred across the backend boundary and converted host-side, so a document inside a sandbox reads the same as a local one.
|
|
|
|
## Scanned PDFs: the coverage warning
|
|
|
|
PDF conversion reads the **text layer only**. Pages that are scanned images — common in legal documents, resale packages, signed contracts, faxes — contain no text layer and silently convert to nothing. The telltale signature is section headers with empty bodies.
|
|
|
|
When a meaningful share of pages yields no text (over 20% of the document, or 10+ pages absolute), `read_file` prepends a warning to the extraction. Each unreadable gap is labeled with the last text extracted before it — usually a section divider — so the agent can target only the gaps it actually needs instead of OCRing the whole document:
|
|
|
|
```
|
|
[EXTRACTION COVERAGE WARNING: 198 of 311 pages in this PDF yielded no
|
|
text. ... Unreadable gaps, each labeled with the last text extracted
|
|
before it:
|
|
pages 42-77 (36 pages) — after "Antigua Maintenance Corp Bylaws" (p41)
|
|
pages 92-213 (122 pages) — after "... Covenants, Codes and Regulations" (p91)
|
|
page 224 (1 page) — after "... Insurance Declaration Pages" (p223)
|
|
Decide which gaps you actually need — do NOT OCR or render everything. ...]
|
|
```
|
|
|
|
The warning lists the exact page ranges and the recovery paths:
|
|
|
|
1. **A few pages — render + vision.** Convert the pages to images and read them with the vision tool:
|
|
```bash
|
|
pdftoppm -jpeg -r 150 -f 92 -l 94 document.pdf $TMPDIR/page
|
|
```
|
|
Then inspect each image with `vision_analyze`. Zero extra dependencies (poppler is required for the detection itself).
|
|
2. **Many pages — OCR.** The `ocr-and-documents` skill covers bulk OCR with marker-pdf (90+ languages, handles equations and tables; ~3-5 GB install).
|
|
|
|
Detection uses poppler's `pdftotext` for per-page text counts. If poppler is not installed, extraction still works — the coverage check is silently skipped.
|
|
|
|
:::tip
|
|
The agent handles the warning on its own — it will offer to render or OCR the missing pages. If you're reading extractions yourself, treat "header with an empty body" as a scanned section, not a missing one.
|
|
:::
|