wolfsuite

Technical Study · Runtime Boundaries

What happens when an AI agent edits a spreadsheet? Inside headless LibreOffice's silent OOXML rewrites

The conversation right now is almost entirely about agent reasoning models: can the model plan financial models, audit revenue, or write Python? Nobody asks what happens when the agent actually saves the file.

CORPUS: 5 FIXTURES·WOLFXL 2.1.0 VS LIBREOFFICE 24.2.7.2·16-VCPU LINUX (VERDA)·RECEIPT-BACKED

The wrong question

Evaluating an AI agent on how well it formulates formulas or generates Python code misses the critical failure boundary. In production agent workflows, the primary point of failure is the file runtime.

When an agent needs to read, recalculate, or update a workbook, developers face three flawed compromises:

01 · Headless LibreOffice

Spawns a 410 MB desktop suite inside the container. Requires user-profile workarounds, emits noisy stderr on clean exits, and re-serializes the entire ZIP package on every save.

02 · openpyxl DOM rebuild

Deserializes the workbook into a heavy in-memory Python DOM. On save, it strips or alters charts, pivot tables, macros, and relationship graphs.

03 · XlsxWriter

Fast and reliable for greenfield creation, but strictly write-only. It cannot open, read, or edit an existing corporate spreadsheet template.

To edit a single cell, containerized agent harnesses resort to the first option: headless LibreOffice.1 Here is what happens next:

Untouched parts drifted

0

WolfXL rewrote 0 untouched parts on a single-cell save. Headless LibreOffice rewrote 8 to 151.

16-vCPU Linux sweep

12-0

WolfXL swept every registered task row: bulk CSV, roundtrip save, and PDF export.

200k roundtrip save

15.94x

0.424 s with WolfXL versus 6.753 s with headless LibreOffice on the same 200,000-row fixture.

Silent package drift: diffing the raw OOXML

An Excel workbook (.xlsx or .xlsm) is an Open Packaging Conventions (OPC) zip archive containing dozens of interconnected XML parts: worksheet data, shared string tables, styles, themes, drawing relationships, chart definitions, and pivot caches.

Now consider this: when an agent updates cell B2, only cell B2 changes. Styles tables, drawing relationship trees, and theme palettes remain untouched: a surgical patch directly into the ZIP archive.

We tested what happens when an agent modifies a single cell across five real-world spreadsheet fixtures:

  • A financial reporting model with chart linkages and drawing parts.
  • A template with external hyperlinks and custom document properties.
  • A formatted workbook with custom theme palettes and conditional styling.
  • A high-density table with shared string references.
  • A macro-enabled operational pack (XLSM).

Here is the same workbook, saved twice. The only requested change in both runs was cell B2, which lives in xl/worksheets/sheet1.xml:

wb.xlsx · LibreOffice single-cell save11-13 unrelated parts rewritten
[Content_Types].xmlM
docProps/app.xmlM
docProps/core.xmlM
xl/workbook.xmlM
xl/_rels/workbook.xml.relsM
xl/sharedStrings.xmlM
xl/styles.xmlM
xl/theme/theme1.xmlM
xl/drawings/drawing1.xmlM
xl/drawings/_rels/drawing1.xml.relsM
xl/worksheets/sheet1.xmlM
wb.xlsx · WolfXL modify-mode save0 unrelated parts rewritten
[Content_Types].xml·
docProps/app.xml·
docProps/core.xml·
xl/workbook.xml·
xl/styles.xml·
xl/theme/theme1.xml·
xl/drawings/drawing1.xml·
xl/worksheets/sheet1.xmlM

Representative part set from the preservation receipts. Amber M = the part containing the edited cell. Rose M = an untouched part rewritten anyway. Dotted = byte-identical. Across the five-fixture study the unrelated-part count ranged from 8 to 151.

The observation: On a single-cell save, headless LibreOffice rewrote between 8 and 151 untouched internal parts across the fixtures.2 It re-serialized XML trees, dropped non-standard namespaces, reordered style index tables, and rebuilt drawing relationship graphs.

What a rewritten part looks like on the inside

Even though cell B2 has no drawing or chart attached, LibreOffice parses and re-serializes xl/drawings/drawing1.xml and xl/styles.xml, rewriting namespace prefixes and reordering relationship IDs:

xl/drawings/drawing1.xml (LibreOffice single-cell save)
@@ -1,12 +1,10 @@
- <xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
-            xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
-            xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
+ <xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
    <xdr:twoCellAnchor editAs="oneCell">
-     <xdr:from><xdr:col>4</xdr:col><xdr:colOff>0</xdr:colOff><xdr:row>2</xdr:row><xdr:rowOff>0</xdr:rowOff></xdr:from>
-     <xdr:to><xdr:col>9</xdr:col><xdr:colOff>12700</xdr:colOff><xdr:row>16</xdr:row><xdr:rowOff>50800</xdr:rowOff></xdr:to>
-     <xdr:graphicFrame macro="">
-       <xdr:nvGraphicFramePr><xdr:cNvPr id="2" name="Chart 1"/><xdr:cNvGraphicFramePr/></xdr:nvGraphicFramePr>
-       <a:graphic><a:graphicData uri=".../chart"><c:chart xmlns:c="..." r:id="rId2"/></a:graphicData></a:graphic>
+     <xdr:from><xdr:col>4</xdr:col><xdr:row>2</xdr:row></xdr:from>
+     <xdr:to><xdr:col>9</xdr:col><xdr:row>16</xdr:row></xdr:to>
+     <xdr:graphicFrame>
+       <xdr:nvGraphicFramePr><xdr:cNvPr id="1" name="Object 1"/></xdr:nvGraphicFramePr>
+       <a:graphic><a:graphicData uri=".../chart"><c:chart xmlns:r="..." r:id="rId1"/></a:graphicData></a:graphic>
    </xdr:twoCellAnchor>

What WolfXL modify mode changes

WolfXL patches the specific byte range of the target cell in xl/worksheets/sheet1.xml. Every other part in the zip container remains byte-for-byte untouched:

xl/worksheets/sheet1.xml (WolfXL surgical save)
@@ -14,5 +14,5 @@
     <row r="2" spans="1:4">
       <c r="A2" t="s"><v>0</v></c>
-      <c r="B2"><v>1000000</v></c>
+      <c r="B2"><v>1250000</v></c>
       <c r="C2" s="1"><f>B2*0.2</f><v>200000</v></c>
     </row>
# 0 other files modified (styles, drawings, themes, and relations unchanged)
BENCHMARK MATRIX·16-VCPU DEDICATED LINUX (VERDA)·200,000 ROWS × 8 COLS·12-0 SWEEP

Latency on real workloads: 12-0 benchmark sweep

Spawning a 410 MB desktop suite inside an agent container also carries substantial execution latency. We benchmarked WolfXL 2.1.0 against LibreOffice 24.2.7.2 on a dedicated 16-vCPU x86_64 Linux system using a 200,000 row by 8 column dataset across 12 registered task routes.3

200k-row CSV extract

5.25x faster

WolfXL
1.545 s
LibreOffice
8.114 s

200k-row roundtrip save

15.94x faster

WolfXL
0.424 s
LibreOffice
6.753 s

PDF export

2.84x faster

WolfXL
0.434 s
LibreOffice
1.230 s

Small-file CSV extract

3.82x faster

WolfXL
0.225 s
LibreOffice
0.860 s

Recalculate and extract

3.26x faster

WolfXL
0.264 s
LibreOffice
0.859 s

Bars normalized per row against the slower engine; seconds printed. Full matrix below.

Registered taskWolfXL medianLibreOffice medianObserved ratioScope
200k CSV extraction1.545 s8.114 s5.25x faster200,000 × 8 fixture
200k roundtrip save0.424 s6.753 s15.94x faster200,000 × 8 fixture
PDF export0.434 s1.230 s2.84x fasterRenderable styled fixture
Small-file CSV extract0.225 s0.860 s3.82x faster1,000 × 8 fixture
Formula recalculation0.264 s0.859 s3.26x fasterRecalc and extract mode
Surgical cell update0.032 sUnsupportedWolfXL onlySingle-cell patch

Across every tested route, WolfXL outputs matched expected values.4 Cold start, warm latency, bulk CSV, roundtrip save, and PDF conversion were all won by WolfXL in this measured matrix.

CONTAINER TELEMETRY·IN-PROCESS ABI3 VS 410 MB DESKTOP BUNDLE

Container footprint and process overhead

For autonomous agent infrastructure, disk footprint and process initialization directly govern container provisioning speed, ephemeral disk costs, and memory ceiling under concurrency.

WolfXL ABI3 wheel37.6 MB
LibreOffice install tree422.8 MB

What an agent container actually pulls: the WolfXL wheel against the observed LibreOffice installation tree. Receipt-backed installation-tree comparison (39.4 MB vs 422.8 MB): 10.7x.

Peak RSS

49.7 vs 59.4 MiB

On the 200,000-row workload, WolfXL recorded a 16.5% lower peak memory footprint across the complete execution lifetime.

Process lifecycle

In-process

WolfXL runs directly inside the Python process. LibreOffice requires spawning a separate desktop process with profile directory scaffolding.

Dependency surface

ABI3 wheel

One wheel covers Python 3.9 through 3.13 with no native toolchain, no compiler, and no system libraries at install time.

Reproducibility and evidence receipts

All benchmark scripts, synthetic fixtures, diff tools, and SHA-256 evidence receipts are public in the WolfXL open-source repository so you can inspect the exact methods and verify numbers on your own hardware.