Paper Instruments
← Research

Introducing Paper Office


paper-docx
paper-pptx
paper-xlsx

Today, we release Paper Office, a suite of Python packages that allow agents to manipulate Word, PowerPoint, and Excel files with added safety, correctness and breadth, building on legacy open source packages: python-docx, python-pptx and OpenPyxl. Across five models and 61 tasks, Paper packages plus guidance passed 92.5% of trials, versus 80.7% for upstream packages without skills and 69.5% with Anthropic's comparable Office skills. Agents also wrote code to edit Office file internals directly in just 1.6% of Paper runs, compared with 78.7% without skills and 50.5% with Anthropic skills. We note that basic software, in addition to prompt, skills, and tools, remains an important lever for harness optimization.

Brownfield Knowledge Work

Agents still have low penetration in the daily work of consultants, lawyers, bankers, and operators. We believe the bottleneck to professional adoption is fidelity to real workflows. Agents dont manipulate existing documents with the same techniques that humans do, and the resulting decks, sheets, and documents sit in an uncanny valley that aren't fit for client consumption.

DOCX, PPTX, and XLSX files use Office Open XML (OOXML): each is a ZIP archive containing XML files, images, and other resources linked together, rather than a single text file. Editing them means keeping those parts and their relationships consistent, so even a small visible change can require updates in several places.

The standard Python Office libraries (python-docx, python-pptx, and openpyxl) are mature construction tools with years of accumulated edge cases, and most production agents often rely on these libraries for doc manipulation. However, they haven't seen updates in several years and, for many important workflows, lack feature breadth and correctness contracts. In some cases, models opt for other Javascript based packages or HTML-to-document processes to more easily represent and manipulate classic office documents, but these intermediate representations are lossy and often corrupt existing, brownfield work.

We forked, patched, and reworked the APIs of the standard Python Office libraries to support a variety of agent-first use cases, improving correctness and expanding feature completeness.

An agent editing a file often has to:

  • find the logical object a user refers to, even when its text is split across elements or inherited from a template
  • apply the requested change while maintaining formatting and relationships
  • preserve everything outside the requested scope, including package parts the library does not understand
  • reopen the saved artifact and establish that the intended effect survived serialization.

When the package cannot express those operations, the model falls through to wrapper scripts and raw OOXML, polluting its context with package mechanics. This frequently leads to silent regressions in comment anchors, chart workbooks, fields, custom XML, formula dependencies and so on.

Paper Office Packages

Paper Office keeps the familiar imports and extends the packages underneath them. Existing model priors remain useful: import docx, from pptx import Presentation, and import openpyxl still work.

The additions expose hidden structure as typed, machine-readable data, validate targets before supported edits, provide package-preserving save paths, report bounded changes, and refuse explicitly when an operation cannot be handled safely.

Paper DOCX

Overview

paper-docx extends python-docx with document-wide search, tracked edits, comments, composition, and reversible redlines, allowing agents to revise and review existing Word documents using Word's native review model.

Explore Features

The additions, tied to the work:

  • Cross-Run Text Search. docx.search matches exact text across Word's run fragmentation, with opt-in normalized matching, and returns a live Span for replacement, tracked edits, or comment anchoring. Spans revalidate before mutation; reacquire a span after a text-changing replacement.
  • Numbering Restarts. docx.numbering.restart_numbering() creates a new numbering instance that retains the existing definition and restarts at one; apply_numbering() assigns it to the intended paragraphs.
  • Native Word Redlines. docx.package.compare emits text and table-row changes as Word-native tracked changes, verifies both accept and reject outcomes on private copies, and refuses differences it cannot represent safely.
  • Review Resolution and Comment Threads. doc.revisions enumerates supported revisions and accepts or rejects them atomically; remaining_unsupported() identifies forms requiring another tool. docx.commentops manages replies, anchors, and resolution state as a connected thread.
  • Content Controls. docx.controls fills supported controls with typed values. Supported data-bound text updates synchronize visible text with the custom XML store; unsupported bound types, locked controls, and unsafe structures refuse.
  • Fields and Bookmarks. docx.bookmarks and docx.fields create bookmarks over a span and author page numbers, dates, cross-references, and tables of contents as fields with placeholder results.
  • Cross-Document Composition. docx.composition copies formatted content between documents, reconciles styles, numbering, media, hyperlinks, and bookmarks, and reports every part touched.
  • Package Diffs and Saves. docx.package.patch_save restores original bytes for semantically unchanged package parts; diff_package and text_diff report changes, and diagnose explains unreadable packages. Normal Document.save() validates its output but does not promise byte-minimal serialization.
  • Document Protection. docx.protection checks Paper mutations against the active restriction mode: comments-only protection permits comment operations, and forms protection permits supported form updates. An explicit acknowledgment overrides the guard without removing the protection setting.

The fork also traverses body text, headers, footers, footnotes, endnotes, comments, tracked insertions, content controls, and text boxes through docx.story, with revision views and counts of blind regions it cannot read.

Paper PPTX

Overview

paper-pptx extends python-pptx with inherited-format inspection, native bullets, guarded slide and shape edits, cross-deck composition, and structural diffs, enabling surgical diffing of PowerPoint files.

Explore Features

The additions, tied to the work:

  • Effective Formatting Inspection. pptx.inspect, inspect_text, and inspect_deck resolve supported font, paragraph, and shape formatting through the placeholder, layout, master, and theme chain, with provenance and content-fingerprinted BlockAnchor targets. Unsupported values are marked unresolved and unreadable regions are counted.
  • Formatting-Preserving Text Replacement. pptx.edit.replace_text, replace_text_at, and refind replace text while preserving unaffected runs, and anchored edits detect stale content and refuse before a change lands on the wrong text.
  • Relationship-Safe Slide Cloning. prs.slides.clone(), delete(), reorder(), and move() are relationship-safe; cloned charts receive independent embedded workbooks, and unsupported relationships raise typed refusals.
  • Package-Safe Shape Editing. SlideShapes.delete(), move(), and add_copy() edit shapes while preserving ownership, and group-aware by-name lookup refuses ambiguous names and asks the caller to disambiguate.
  • Merged-Cell Table Editing. Table.insert_row(), delete_row(), insert_column(), and delete_column() keep the grid consistent and guard merged regions at the affected cells.
  • Bullets, Numbering, and Notes. Paragraph.bullet and TextFrame.normalize_autofit() author real bullets and make inherited autofit explicit. Slide.read_notes_text() reads without creating a notes part; replace_notes_text() edits an existing notes body.
  • Safe Image and Chart Updates. Picture.replace_image() changes only the target picture while preserving position and crop, even when an image part is shared. Chart.replace_data_safe() validates the target chart and workbook structure, refusing shared chart parts and unsupported chart families.
  • Policy-Based Slide and Deck Imports. Presentation.import_slide() and append_deck() require an explicit reconciliation mode: adopt the destination theme, keep the source appearance, or bake effective values into explicit formatting. They return an ImportReport for each imported slide.
  • Layout Rebinding. Slide.rebind_layout() moves a slide to another layout under explicit placeholder and orphan policies, and its RebindReport identifies every run whose resolved appearance changed.
  • Live Slide Number and Date Fields. Presentation.apply_footers() and Slide.apply_footers() author native a:fld elements bound to footer placeholders. Date formats use datetime1 through datetime13; fixed_date instead writes literal text.
  • Package-Preserving Saves. pptx.package.patch_save() restores original bytes for semantically unchanged members, so unrelated serialization changes need not appear in the delivered package.
  • Permanent-ID Deck Diffs. pptx.diff.diff_decks reports structural changes in decks derived from a common ancestor. detail="text" adds text, chart-data, and notes deltas; detail="full" adds resolved run-formatting and bullet changes.

Intake is hardened too: the package rejects ambiguous or unsafe ZIP archives, including duplicate or case-colliding members, noncanonical paths, encryption, and unsupported compression. Path-based saves write a sibling temporary package and atomically replace the destination after serialization succeeds.

Paper XLSX

Overview

paper-xlsx extends openpyxl with package-preserving saves, reference-aware structural edits, edit receipts, and LibreOffice-backed recalculation, supporting diffs that account for formulas, dependencies, and package content.

Explore Features

The additions, tied to the work:

  • Targeted Workbook Inspection. Standard surfaces such as wb.sheetnames, bounded cell ranges, and wb.defined_names remain the starting point. wb.search() finds text or regex matches in values and formulas; ws.allowed_values(cell) reports validation-derived choices.
  • Reference-Aware Structural Edits. Supported row, column, sheet, and range operations update dependent formulas, names, print areas, table ranges, and chart references, or refuse. Row and column insertions and deletions return an AddressRemap; sheet renames and move_range() do not.
  • Preserve-Mode Object Editing. copy_format() copies formatting, chart.repoint() updates a value series and removes its stale cache, ws.append_table_row() expands a supported table atomically, and ws.replace_image() replaces one loaded image without rewriting the drawing. Category ranges and the intended business scope still need explicit attention.
  • Formula Cache Freshness. Formula edits and input changes that may feed formulas invalidate retained cached results and request recalculation on open. Style-only and unrelated value edits keep their caches. Until recalculation, a data-only reader may see None; Paper does not calculate formulas itself.
  • Error Inspection and Diffs. openpyxl.preserve.scan_errors() inspects formula and cached/value error representations without LibreOffice. diff_workbooks(..., remaps=()) separates content changes from cells shifted by structural edits.
  • Edit Receipts. wb.save(..., receipt=True) returns an EditReceipt naming changed cells and parts; wb.validate() runs save validation without writing. A receipt can include formula-cache invalidations as well as the cell the agent explicitly edited.
  • LibreOffice Recalculation and Certification. oracle.recalc() recalculates a temporary copy, scans for errors, and can write a separate Paper-preserved candidate through output_path. oracle.certify() reports whether LibreOffice reproduces cached values as CERTIFIED, DIVERGED, or BASELINE_UNVERIFIABLE. oracle.evaluate() and oracle.evaluate_many() apply temporary inputs and return requested outputs. These operations never overwrite the source, and preservation does not require LibreOffice.
  • Protection and Pivot Refresh. Writes to locked cells can warn; wb.strict_protection = True makes them refuse. wb.set_pivot_refresh_on_load() grants explicit consent for dependent edits and asks Excel to refresh the selected pivots on open. Their cached results remain stale until that refresh.

Path saves build the archive on disk, validate ZIP consistency, and fsync before rename. Callers, rather than fixed package-wide size or compression-ratio limits, control resource budgets.

Experimental Design

The reported comparison covers 61 tasks: 27 DOCX, 15 PPTX, and 19 XLSX. It covers a wide range of existing-file workflows involving fragmented text, revisions, comment threads, numbering, fields, slide relationships, embedded workbooks, template lineage, formulas, names, charts, and dependent ranges, alongside two creation controls.

We evaluated five models: Opus 5, GLM 5.3 Flash, GPT 5.6 Sol, Grok 4.6, and DeepSeek v4.1 Flash. Each model-task pair is rolled out under each of three conditions:

ConditionPackageGuidance
No skillsUpstreamNone
Anthropic skillsUpstreamAnthropic's Office skills
Paper + skillsPaperPaper skills

All conditions ran through OpenCode in Harbor, with identical task prompts across conditions.

Both upstream conditions used the same pinned versions: python-docx==1.2.0, python-pptx==1.0.2, and openpyxl==3.1.5.

Task Success

Task success by model

Legacy PackagesLegacy + Anthropic SkillsPaper Packages

Opus 5

GLM 5.3 Flash

GPT 5.6 Sol

Grok 4.6

DeepSeek v4.1 Flash

All five models

Paper passes 282/305 trials (92.5%), compared with 246/305 (80.7%) upstream raw and 212/305 (69.5%) with Anthropic skills.

Performance by Slice

Task success by format

Legacy PackagesLegacy + Anthropic SkillsPaper Packages

DOCX

PPTX

XLSX

Against no skills, Paper improves success in 12 of 15 model-by-format slices, ties on DeepSeek PPTX and XLSX, and trails by one task on GLM PPTX. Pooled across models:

  • DOCX: 83.0% without skills, 84.4% with Anthropic skills, 96.3% with Paper.
  • PPTX: 84.0% without skills, 74.7% with Anthropic skills, 92.0% with Paper.
  • XLSX: 74.7% without skills, 44.2% with Anthropic skills, 87.4% with Paper.

Trajectory Analysis

The main difference was who handled the file's internal bookkeeping. In the reviewed upstream runs, agents often wrote code to reconnect comments, copy slide relationships, or repair spreadsheet references and caches. Some did this successfully. Paper usually handled that work through its APIs, although agents still inspected XML to check the result. Across all 915 retained runs, explicit raw ZIP editing code appeared far more often with upstream packages:

Direct edits to OOXML internals

Runs where agents wrote code to update files inside the Office archive directly, including attempts and scratch tests.

The recurring failure in the reviewed cases was an incomplete edit: the requested text or numbers were right, but a comment link, dependent range, or unrelated part of the file was not. Paper helped with those connected changes.

How we counted trajectory behavior

We analyzed the selected run for each model, task, and condition across the 61 tasks. The chart counts a run once if agent-submitted code contains an explicit .writestr(...) call. We inspected source submitted through inline Python, script heredocs, file writes, edits, and added patch lines, excluding returned documentation and library source.

Across the trials, Paper found formula errors that upstream runs missed, kept comment replies attached when a thread was edited, and updated spreadsheet validation and formatting ranges when a column was inserted. Even if the edit was simple to make, without Paper the model often corrupted the surrounding document in subtle ways.

The following examples compare how the same model completed the same task with each setup.

Example 1: DOCX Comments

DeepSeek was asked to extend an existing comment, add a reply, and mark the conversation as resolved while leaving another discussion open. Using the upstream package without skills, it added the requested text but did not correctly update the links that connect the replies and record the conversation's resolved status. Paper maintained those links through its comment APIs, while the agent using Anthropic skills updated them manually. Both passed, while the version without skills failed.

Example 2: PPTX Slide Copying

Grok was asked to copy a slide and change the copy's chart and speaker notes without affecting the original. All three setups passed the checks for keeping the chart and notes independent. The copies made with the upstream package retained internal creation identifiers from the original slide, and this was their only failed check. Paper assigned new identifiers when copying the slide and passed all checks.

Example 3: XLSX Forecast Updates

GLM was asked to add a forecast quarter and update the chart and annual totals. All three setups reached the correct revenue of 485 and EBITDA of 111. With the upstream package, some input-validation and formatting rules still covered the old cell ranges, and the agent without skills left the old quarter in the chart. Paper updated the dependent ranges, and the agent produced a workbook that passed all checks. The other two setups got the totals right but left parts of the workbook out of date.

A separate spreadsheet task asked GPT 5.6 Sol to identify formula errors without changing the file. Using Paper's scan_errors(), it found all four required errors in the formulas and stored calculation results. In both upstream setups, it found only two. The Paper result passed, and the workbook remained unchanged.

Software as a Performance Lever

Across five models, Paper packages increased complete task success across the board, and highlight software as an important lever to improve agent performance for knowledge work problems.

We use Paper Office by default inside the Feather harness. Learn more about the packages here.

You can see all tasks here.


Citation

Khazi, Daanish; Bains, Gavin; and Besgen, Joey, "Introducing Paper Office",
Paper Instruments Blog, September 23, 2026.

@misc{khazietal2026paperoffice,
  author = {Daanish Khazi and Gavin Bains and Joey Besgen},
  title = {Introducing Paper Office},
  howpublished = {Paper Instruments Blog},
  year = {2026},
  month = {September},
  url = {https://www.paperinstruments.com/blog/introducing-paper-office}
}