Skip to content
yottacode v0.4.0 is out! ๐ŸŽ‰ See the release notes โ†—
Document generation

Document generation

create_document generates a new xlsx, docx, pdf, or pptx file from structured content โ€” the write-side counterpart to read_document, which now also reads .pdf, .xlsx, .docx, and .pptx alongside its original CSV/TSV/JSON/JSONL/ XML/HTML formats. xlsx and pptx generation are native Go paths; docx/pdf generation and PDF parsing shell out to pandoc/pdftotext/pdfinfo. Only those subprocess-backed paths route through the same command Sandbox run_bash uses.

Status: experimental. Enable with --experimental document_generation, YOTTACODE_EXPERIMENTAL=document_generation, or [experimental] document_generation = true in config.

Three generation paths

    ---
title: create_document dispatch
---
flowchart LR
    %% Author: YottaDynamics | Scope: create_document format dispatch
    Tool[create_document] --> Format{format}
    Format -- xlsx --> Excelize[excelize: native Go]
    Excelize --> XLSXFile[(.xlsx file)]
    Format -- docx/pdf --> MD[DocAST to Markdown]
    Format -- pptx --> GoPPTX[Go PPTX renderer]
    MD --> Seam[agent.Sandbox interface]
    Seam -- nil sandbox --> HostCmd[/Host pandoc/]
    Seam -- podman sandbox --> ContainerCmd[podman exec pandoc]
    HostCmd --> OutFile[(.docx / .pdf file)]
    ContainerCmd --> OutFile
    GoPPTX --> PPTXFile[(.pptx file)]
  
  • xlsx never touches the sandbox. Content is described as a sheets array of rows of cells (value, formula, bold, italic, number_format) and rendered directly to xlsx bytes by excelize, which has its own formula-calculation engine โ€” no external tools, works identically with or without a command sandbox configured.
  • docx/pdf content is described as a blocks array (heading, paragraph, list, table, code), rendered to Pandoc-flavored Markdown, then converted by pandoc (pdf additionally passes --pdf-engine=weasyprint).
  • pptx content is described as a slides array (title, bullets, notes, image, image_alt, layout) and rendered by native Go into a minimal Office Open XML presentation. Like xlsx, this path never touches the sandbox and needs no Python runtime. A slide’s image is validated as a read path the same way a docx/pdf image block is; the renderer embeds one local PNG/JPEG/GIF image per slide when supplied.

Every docx/pdf subprocess invocation (pandoc/weasyprint) runs through whatever agent.Sandbox the session has: directly on the host when no sandbox is configured, or via podman exec when [sandbox].backend = "podman" โ€” the exact same seam run_bash uses.

See tools.md#create_document for the full argument reference and examples.

Requirements

FormatRequires
xlsxNothing โ€” pure Go
docxpandoc on PATH (host) or in [sandbox].image
pdfpandoc and weasyprint on PATH (host) or in [sandbox].image
pptxNothing โ€” pure Go

If a required subprocess binary for docx/pdf isn’t reachable through the active sandbox, create_document returns an error naming exactly where it looked (host PATH or the sandbox’s label) instead of failing silently.

Using the reference documents image

infra/documents.Containerfile bundles only the production subprocess dependencies the current code invokes: pandoc for docx/pdf generation and optional rich docx parsing, weasyprint for PDF generation, and poppler-utils (pdftotext/ pdfinfo) for PDF text extraction. xlsx and pptx paths are native Go and never use this image.

A CI workflow that builds, smoke-tests, and publishes this image exists (.github/workflows/documents-image.yml, manual workflow_dispatch plus a weekly rebuild for CVE patches), but it’s never been run โ€” nobody with registry access has triggered it yet, so ghcr.io/yottadynamics/yottacode-documents isn’t live. Build it locally in the meantime:

podman build -t yottacode-documents -f infra/documents.Containerfile .

Then point the command sandbox at it and enable both experimental flags:

[experimental]
sandbox             = true
document_generation = true

[sandbox]
backend = "podman"
image   = "yottacode-documents"

Once the workflow has actually been run at least once, the published tag works the same way โ€” image = "ghcr.io/yottadynamics/yottacode-documents:latest" โ€” with no other config changes.

Everything else about the sandbox โ€” lifecycle, mounts, network policy, hardening โ€” is unchanged; see sandbox.md for the full picture. create_document’s docx/pdf paths and read_document’s PDF path are the document tools routed through that seam besides run_bash.

read_document’s PDF path (pdftotext/pdfinfo) follows the same availability contract as create_document’s pandoc path: a missing binary is an actionable error naming where it was checked (host PATH or the sandbox label), not a silent empty result. An encrypted or scanned/image-only PDF is reported as a warning instead, since that’s still a valid, actionable result โ€” see tools.md#read_document for the full argument reference.

Known limitations

  • xlsx/docx/pptx parsing and PDF parsing are structural-only: no tables, images, complex formatting, or embedded objects โ€” that’s pandoc’s job, and only wired for generation so far, not parsing.
  • pptx generation covers title + bullets + speaker notes + one image per slide in a fixed native-Go layout โ€” no tables, charts, multiple images per slide, custom template themes, or precise image positioning yet. image_alt is written to the picture description field for consumers that surface OOXML alt text.
  • No published yottacode-documents image yet โ€” the publish workflow exists (.github/workflows/documents-image.yml) but has never been triggered; build the Containerfile locally until someone with registry access runs it.
  • create_document only ever writes local files; there is no URL output or cloud storage integration.
  • Table cells and free text are Markdown-escaped before reaching pandoc, so a table cell can’t be used to inject arbitrary Markdown/HTML structure. Headings, paragraphs, and list items support inline bold/ italic formatting via spans/item_spans (structured runs, not a raw markdown string โ€” each span’s text still goes through the same escape pass); table cells don’t have a spans equivalent yet, only plain text.
  • docx/pdf blocks support one image type (local file path + alt text, validated as a read path the same way read_file validates one) โ€” no inline images within a paragraph, no image sizing/positioning control. pptx slides support one image per slide the same way โ€” no inline images within bullet text.