Skip to content

Document ingestion

Upload a document and Martha turns it into something agents can search. PDF, markdown, HTML, image — all the same path: parse, chunk, embed, index. When the pipeline finishes, every document tool (keyword, semantic, visual, page-image) sees the result.

How it works

Upload → Validate → Parse + chunk → Enrich (in parallel) → Finalize
           │            │               │                       │
           │            │               ├─ Vector embed         └─ Mark "ready"
           │            │               ├─ Vision-model describe
           │            │               └─ Visual index
           │            └─ Layout-aware parse + chunk + page classify
           └─ Size, type, and tenant checks

Each stage runs independently with its own retry and timeout policy. The three enrichment steps run in parallel and are non-fatal — failures degrade gracefully (e.g. lose semantic search but keep keyword search) rather than blocking the document from being usable.

Page rendering and visual processing

For PDFs, every page is rendered to PNG and uploaded to storage (up to MAX_RENDER_PAGES, default 300). This enables:

  • The get_page_image and visual_search tools to return page images to agents.
  • Visual indexing for image-first retrieval across all pages — useful for diagrams, schematics, charts.

Pages are classified as text, drawing, or table based on layout. Drawing pages get extra treatment:

  • Vision-model descriptions of the page content, stored as searchable description chunks. This means agents can find a diagram by what's in it, not just by surrounding text.

All enrichment is optional and non-fatal — if any of it fails, keyword search still works.

Supported Formats

FormatContent TypeNotes
PDFapplication/pdfFull text extraction with optional OCR
DOCXapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentWord documents
PPTXapplication/vnd.openxmlformats-officedocument.presentationml.presentationPowerPoint
HTMLtext/htmlWeb pages
Markdowntext/markdownMarkdown files
CSVtext/csvTabular data
Imagesimage/png, image/jpeg, image/tiff, image/webp, image/bmpOCR text extraction
JSONapplication/jsonStructured text — read as-is, chunked by character span
YAMLapplication/yaml, application/x-yaml, text/yaml, text/x-yamlStructured text — read as-is, chunked by character span
XMLapplication/xml, text/xmlStructured text — read as-is, chunked by character span
Plain texttext/plainRead as-is, chunked by character span

!!! info "Structured text formats skip layout analysis" JSON, YAML, XML, and plain text content types are decoded as UTF-8 and passed straight to the chunker — they don't go through the document layout pipeline (no page rendering, no OCR, no vision-model description). This is faster (sub-second for most files) and avoids spurious "no parseable content" errors from the layout parser on what is genuinely just text. Pages are reported as 0; chunk count reflects the file's character count divided by the chunk-size token budget.

!!! info "Maximum file size" The default maximum is 50 MB per document. This can be adjusted via the INGESTION_MAX_DOC_SIZE environment variable.

Graceful Degradation

The enrich stage (embedding, VLM descriptions, visual indexing) is not required for a document to be usable. If any enrichment fails:

  • The document is still marked as "ready"
  • Chunks are stored with full text (keyword search works)
  • Semantic search is unavailable if embeddings failed
  • Drawing descriptions are unavailable if VLM failed
  • Visual retrieval is unavailable if ColPali indexing failed
  • All can be backfilled by re-ingesting

Monitoring Ingestion

Admin UI

The Documents page shows ingestion status for each document:

  • Gray badge — Pending (not yet started)
  • Blue badge with spinner — Ingesting (workflow running)
  • Green badge — Ready (ingestion complete)
  • Red badge — Error (with details)

When any document is actively ingesting, the page auto-refreshes every 3 seconds. Click on a document to see detailed progress: current stage, percentage, chunk count, and any errors. Hover a red Error badge to see why that document failed (the parser error).

For bulk uploads, two aggregate views summarize progress without scrolling the table:

  • Collection (Documents page): a strip under the collection header shows the subtree totals — N documents · R ready · I in progress · F failed — with a Retry F failed button when there are failures.
  • Sync source (Documents → Sources → a source): an Overview panel shows a progress bar, counts, the recent drain rate + a naive ETA, and a table of failed documents with per-document reasons and a Retry action.

Both retry actions are bounded and safe to run on thousands of documents — see Retrying failed documents.

API

Poll the per-document status endpoint for one document:

bash
GET /api/admin/documents/{document_id}/ingestion-status?tenant_id=your-tenant

Response:

json
{
  "document_id": "a1b2c3d4-...",
  "ingestion_status": "ingesting",
  "stage": "embed",
  "progress_pct": 65,
  "chunk_count": 42,
  "revision_id": "e5f6g7h8-...",
  "error": null,
  "started_at": "2026-02-17T10:30:00Z",
  "completed_at": null
}

Stages progress through: validatecreate_revisionparse_and_chunkenrichfinalizedone.

Aggregate status (collection or sync source)

For bulk ingest, query the aggregate status of a whole collection (and its sub-collections) or a sync source:

bash
GET /api/admin/collections/{collection_id}/ingestion-status
GET /api/admin/document-sync/sources/{source_id}/status

Both return the same shape — counts by status, a recent drain rate, a naive ETA, and the top grouped error reasons:

json
{
  "collection_id": "a1b2c3d4-...",
  "total": 248,
  "counts": { "pending": 0, "ingesting": 4, "ready": 231, "error": 13, "other": 0 },
  "drain_rate_per_min": 2.1,
  "drain_window_minutes": 15,
  "completed_in_window": 32,
  "eta_seconds": 360,
  "top_errors": [
    { "reason": "docling parse timeout after 120s", "count": 9 },
    { "reason": "unsupported content type: application/x-empty", "count": 4 }
  ]
}
  • The collection view is subtree-scoped by default (pass ?include_descendants=false for direct children only).
  • ?window_minutes=60 widens the drain-rate window (1–1440).
  • eta_seconds is null when nothing is outstanding or the pipeline is idle.

List the failed documents (with each one's parser error) for either scope:

bash
GET /api/admin/collections/{collection_id}/ingestion-errors
GET /api/admin/document-sync/sources/{source_id}/errors

Re-Ingestion

To re-process a single document (e.g., after changing chunking settings or to backfill embeddings):

bash
POST /api/admin/documents/{document_id}/reingest?tenant_id=your-tenant

This creates a new revision — the previous revision and its chunks are preserved. The read_doc and search_docs tools always use the latest successful revision.

!!! warning "Concurrent re-ingestion" A document that is already being ingested cannot be re-ingested simultaneously. The API returns 409 Conflict until the current workflow completes.

Retrying failed documents

To re-drive every failed document in a collection (and its sub-collections) or a sync source at once:

bash
POST /api/admin/collections/{collection_id}/retry-ingestion?status=error
POST /api/admin/document-sync/sources/{source_id}/retry-ingestion?status=error
json
{ "collection_id": "a1b2c3d4-...", "statuses": ["error"], "reset_count": 13 }

This is bounded, idempotent, and safe at scale: it flips the matching documents back to pending and lets the ingestion reconciler re-drive them through the per-tenant concurrency limit at a controlled rate — so retrying thousands of documents never stampedes the workers. It never disturbs documents that are currently ingesting or already ready. Pass status=pending to re-drive stuck-pending documents instead.

In the admin UI, the same action is the Retry N failed button on the collection summary strip and on the sync-source panel. From the CLI:

bash
martha documents retry --collection my-collection --status error --yes

Revisions

Each ingestion run creates an immutable revision:

  • Stores the raw parsed text and structured metadata (pages, sections)
  • Preserves a content hash for deduplication
  • Old revisions and their chunks remain in the database
  • read_doc always serves from the latest successful revision (no S3 download needed)
  • Revision number auto-increments per document

Backpressure

To prevent a single tenant from overwhelming the ingestion pipeline, Martha enforces a per-tenant concurrency limit (default: 5 concurrent workflows).

When a tenant hits the limit, upload and re-ingest requests return 429 Too Many Requests. The quota is released automatically when a workflow completes (success or failure) or after a TTL safety valve expires (default: 1 hour).


Configuration

All ingestion settings can be overridden via environment variables:

VariableDefaultDescription
INGESTION_CHUNK_SIZE500Target tokens per chunk
INGESTION_CHUNK_OVERLAP50Token overlap between chunks (~10%)
INGESTION_TOKENIZERcl100k_baseTokenizer model for chunk sizing
INGESTION_EMBEDDING_MODELtext-embedding-3-smallEmbedding model (any LiteLLM-compatible)
INGESTION_EMBEDDING_DIMS1536Expected embedding dimensions
INGESTION_EMBEDDING_BATCH100Chunks per embedding API call
INGESTION_EMBEDDING_RETRIES3Max retries for embedding failures
INGESTION_MAX_CONCURRENT5Max concurrent workflows per tenant
INGESTION_QUOTA_TTL3600Quota safety valve TTL in seconds
INGESTION_PARSE_TIMEOUT600Parse activity timeout in seconds
INGESTION_CHUNK_TIMEOUT120Chunk activity timeout in seconds
INGESTION_EMBED_TIMEOUT300Embed activity timeout in seconds
INGESTION_OCR_ENABLEDtrueEnable OCR for PDFs and images
INGESTION_MAX_DOC_SIZE52428800Maximum document size in bytes (50 MB)
INGESTION_MAX_ACTIVITIES2Max concurrent ingestion stages per worker
INGESTION_VLM_ENABLEDfalseEnable vision-model descriptions for drawing pages
INGESTION_VLM_MODELgemini/gemini-3-flashVision model identifier (any LiteLLM-compatible)
INGESTION_VLM_MAX_TOKENS8192Max tokens for vision-model responses
INGESTION_MAX_DRAWING_PAGES100Max drawing pages to render per document
INGESTION_VISION_RETRIEVAL_ENABLEDfalseEnable image-first visual retrieval
INGESTION_PAGE_IMAGE_DPI144Page rendering DPI (144 = 2x scale)

Martha is built by aiaiai-pt.