When an AI Agent Edits a Spreadsheet, What Happens to the Workbook?¶
I asked a deliberately narrow question: when an AI agent opens an Excel workbook and saves it without changing the workbook's content, what changes inside the file?
The answer matters because an .xlsx or .xlsm file is a ZIP package of interdependent parts. A workbook can contain worksheet XML, styles, validations, comments, external links, charts, pivot caches, slicers, macros, custom XML, and relationship graphs. A one-cell edit should not silently rewrite unrelated structures.
I built WolfXL, a Rust-backed Excel engine for Python, around that preservation constraint. To pressure-test the design, I compared its guarded modify path with the headless LibreOffice route bundled in the local Codex runtime installed with ChatGPT Desktop.
The short result:
- Across five source-identified workbooks, a no-op LibreOffice round trip changed 8 to 151 unrelated OOXML parts.
- WolfXL changed zero unrelated parts on the same five fixtures.
- In a source-matched local benchmark, WolfXL was 2.2x to 3.9x faster on the paired tasks and inputs reported below.
- LibreOffice still covers workflows WolfXL does not, and an isolated idle-footprint probe favored LibreOffice on sampled resident memory.
Every number below points to a compact receipt. The receipts retain unsupported and unfavorable rows.
The preservation test¶
The test is intentionally boring:
- Open a valid workbook.
- Request no content change.
- Save to a new file.
- Compare every OOXML package part against the source.
A changed package part is called package drift here.[^1] A large drift count shows that the save operation reached far beyond the requested no-op.
Results¶
| Workbook fixture | WolfXL unrelated drift | Bundled LibreOffice unrelated drift |
|---|---|---|
| Average by region and quarter | 0 | 8 parts |
| Validation, protection, and custom XML | 0 | 19 parts |
| External links | 0 | 14 parts |
| Pivot chart and slicers | 0 | 31 parts |
| PowerPivot model | 0 | 151 parts |
The preservation result is still operationally important. Agent workflows often open and save the same workbook several times. A broad rewrite on every turn compounds risk, makes reviews noisy, and makes it harder to prove that the agent changed only what the user requested.
The design response: inspect, patch, verify¶
WolfXL's agent-facing write path uses a guarded transaction:
wolfxl-ops inspect 'input.xlsx' --request-id 'agent-mutation-001' > 'inspect-receipt.json'
wolfxl-ops plan 'mutation-request.json' > 'plan-receipt.json'
wolfxl-ops apply 'mutation-request.json' > 'mutation-receipt.json'
The request names the input, output, allowed operations, and allowed ranges. The apply step writes an isolated temporary workbook, verifies the requested change, checks unrelated package drift, emits an attestation, and atomically publishes the destination.
Unsupported operations, non-ready plans, failed verification, and unrelated drift are stop conditions. The route does not silently switch to another engine.
For ordinary Python code, the API keeps the openpyxl shape:
wb = load_workbook("model.xlsx", modify=True)
ws = wb["Inputs"]
ws["B7"] = 0.045
wb.save("model.updated.xlsx")
WolfXL Community is MIT-licensed and available from public PyPI for supported workbook I/O and API fit. Commercial 2.1+ adds current production operations, including the guarded mutation route used in this audit.
Source-matched runtime measurements¶
Preservation is the primary result. I also measured the process cost of calling a bundled office suite beside a spreadsheet-specific engine.
The local benchmark used:
- macOS arm64 on the same machine
- the LibreOffice binary bundled in the inspected Codex runtime
- WolfXL built from commit
35de4e6914854a2dd3e66f5ee69e71729f606ff4 - the resulting CPython 3.13 arm64 wheel installed into a clean environment
- identical fixture inputs per paired task
- three recorded observations per row
- separate cold and warm scenarios
Selected paired medians¶
| Task | Input | WolfXL | Bundled LibreOffice | Ratio |
|---|---|---|---|---|
| CSV extraction, agent-task cold | 10x4 XLSX | 0.303 s | 0.667 s | 2.20x |
| Recalculate and extract, cold | 10x4 formula XLSX | 0.293 s | 0.721 s | 2.46x |
| No-op round trip, cold | 10x4 XLSX | 0.297 s | 0.762 s | 2.56x |
| CSV extraction, cold-start scenario | 10x4 XLSX | 0.333 s | 1.111 s | 3.34x |
| CSV extraction, cold | 20,000x8 XLSX | 0.421 s | 1.503 s | 3.57x |
| No-op round trip, cold | 20,000x8 XLSX | 0.371 s | 1.446 s | 3.90x |
Observed execution times reflect three recorded runs per paired task across cold and warm scenarios.[^2]
Installation and memory¶
The inspected runtime footprint was:
| Runtime | Installed bytes |
|---|---|
| WolfXL benchmark environment | 37,524,645 |
| Bundled LibreOffice | 423,048,828 |
Workload peak process-tree RSS favored WolfXL in the rows above, at roughly 56 to 61 MiB versus 104 to 124 MiB for LibreOffice. The isolated footprint probe went the other direction: 53.1 MiB for WolfXL versus 34.5 MiB for LibreOffice. That probe is not a workload-memory ranking, so I am retaining it as a red result rather than hiding it.
The full benchmark contained 70 observations: 67 completed successfully and three recorded an explicit unsupported result for LibreOffice on the WolfXL-specific surgical-edit task. No unavailable result was converted into an estimate.
Why keep LibreOffice at all?¶
LibreOffice is a capable full office suite.[^3] It handles document and presentation formats, legacy conversion, office-style recalculation, and other workflows outside WolfXL's scope.
The product decision is routing, not replacement:
| Need | Preferred route |
|---|---|
| Supported cell, style, formula, or structural edit where preservation matters | Guarded WolfXL transaction |
| Broad office conversion or unsupported legacy format | LibreOffice fallback |
| Unsupported operation with no approved fallback | Fail visibly |
An agent should know which engine is handling the file, what that engine supports, and whether unrelated state changed before the output replaces the source.
Reproduce it¶
The exact-source public receipts are:
- Preservation receipt, SHA-256
781fc7730468a1cde78998c265a7e02940caf525a439651427033a7d734ece53 - Benchmark receipt, SHA-256
9874b2fbe1d5b1be08628975535ad43f3549b44728dc0239a62a3e9588cf7231 - Exact-source wheel build receipt, wheel SHA-256
9197ed89779e7992b45ae3b5bffef85f0abbe52da1a61ac96d5390550b960bf2 - Audit producer
- Benchmark producer
The measured commit was clean before and after the wheel build, and the wheel was the one used for the published local benchmark.
Try WolfXL¶
If you have a workbook where a one-cell edit must preserve charts, pivots, links, macros, or custom XML, I would like the hardest sanitized fixture you can share.
- Install WolfXL Community
- Inspect the public source
- Run the workflow fit check
- Start a 30-day commercial evaluation
[^1]: Package drift reflects internal ZIP re-serialization rather than guaranteed corruption. Office export filters can normalize namespaces, relationship identifiers, calculation metadata, and drawing geometry without altering visible data. Observations are bounded to the five test fixtures and single no-op round trip. [^2]: Observed ratios describe the recorded adapters, builds, fixtures, and modes on macOS arm64 rather than a universal performance ranking. [^3]: Scope boundaries: LibreOffice is a complete office suite handling documents, presentations, and general conversions; WolfXL is a Python-facing spreadsheet engine. In ChatGPT Desktop, LibreOffice acts as a fallback document adapter; OpenAI's private Walnut engine was outside this comparison. LibreOffice parity does not imply Microsoft Excel correctness.