Get started

Engineering NotesTools and execution

Block Office files that won't open, then show the model its pages

When a model writes code that builds an Excel file, the run can exit with code 0 and the model can answer "Done," while Excel refuses to open the file. If a LET formula's parameter names are missing the _xlpm. prefix, the generation library saves without complaint and Excel won't open the workbook. A file that opens can still be wrong. When the title on slide five overflows its box, nothing in the file structure is broken. The model can't open its own file, so it notices neither problem.

The two kinds of defect show up in different places. Whether a file opens is visible only in its XML; whether the layout is right is visible only when the page is drawn. So we split the check in two. Validators read the file structure, and a file with a defect that keeps it from opening never reaches the user. A file that passes is rendered as page images and attached to the model's next input, and the model decides from the pictures whether to fix anything. This post covers how the validators differ by format, and why the visual check is not a tool the model calls.

Defects that stop a file from opening are invisible on screen, and validators can't see overflowing text

Word, PowerPoint and Excel files are zip archives of XML parts plus relationship definitions between them. Most defects that make an app reject a file live in that XML.

  • When a string pulled from a database or a PDF contains a control character such as a vertical tab, the part is no longer well-formed XML and the app won't open the file. Some generation libraries escape only the five XML special characters and write control characters as they are. So the tool strips these characters before validation (step 3 below).
  • When a slide part's content type declaration is missing, the package default fills the gap and the type becomes application/xml. Both a public schema validator and python-pptx passed that file.
  • Excel strips formula prefixes such as _xlpm. and _xlfn. from what it displays, so you can't tell by looking that one is missing. A cell missing _xlfn. opens but shows #NAME?.

Overflowing text, overlapping shapes and empty slides, on the other hand, happen in perfectly valid XML. Neither check alone catches the other's defects.

There are three common approaches. If the model writes file contents directly as tokens, a one-character XML mistake becomes a file that won't open. If one general-purpose OOXML validator is the pass condition, depending on the format it either catches nothing or blocks healthy files (more on this below). If the model is told "call the check tool when you're done," it skips that step. The measurement showing that an instruction to load the task guide first was almost never followed is in “Tools copy the form and load the guide; models fill the arguments”.

The model writes code, and the server checks that the file really exists

Instead of file contents, the model writes code that uses a document library. For new files that is pptxgenjs for PowerPoint, docx-js for Word, openpyxl for Excel and python-hwpx for Hangul. Code becomes a file in these steps.

How an Office file reaches the user: file check, validation, rendered pages
Figure 1. How an Office file reaches the user: file check, validation, rendered pages
  1. Run. The code runs in an isolated execution sidecar and writes its output straight to the sandbox volume.
  2. File check. Exit code 0 is not evidence of success. If the model catches an error with catch and only prints it, the failure still exits with 0, and if the code doesn't wait for the save, the process ends before the file is written. So the check looks at whether the file actually exists.
  3. Character cleanup. The tool strips characters XML cannot carry (control characters other than tab and newline, noncharacters such as U+FFFE) from the XML parts of the finished file. The file opens even when a generation library wrote such characters as they were. The stripped characters are gone from the text, so the work guide tells the script to clean text whose layout matters before writing it. For Excel, openpyxl rejects such a character the moment it goes into a cell and the script stops, so the guide supplies code that filters it out beforehand.
  4. Normalization. PowerPoint only. It fixes defects the generation library leaves in the same place every time (chart axis counts, element order and the like).
  5. Validation. A per-format validator finds problems and grades them. If even one is fatal, the file does not go to the user.

On a fatal defect, the tool returns a failure that carries each problem's location and how to fix it. For broken XML, that includes which part and which line and column. The model reads this, fixes its code and builds the file again. If a file with the same name was already at that path, the original is put back. A file handed to the user earlier points at that path, and leaving the rejected output there would make the user download the rejected file.

We trust a general-purpose validator differently for each format

Every format first goes through XML parsing and package checks (content types, and whether each relationship points at a part that exists). Parsing is the only check that reports where the XML broke. The public schema validator (openxml-audit, a Python port of the Open XML SDK validation logic) also notices the breakage but doesn't say which line. After that, each format differs.

Format Schema validator Examples of in-house checks
PowerPoint Used, minus two known errors Chart axis references, embedded workbooks
Word Used Missing author on tracked changes, paragraph property order
Excel Not used Formula prefixes, shared string indexes, dynamic array formulas, manual calculation mode
Hangul (hwpx) Separate validator Container conventions, whether the body part is reachable

For PowerPoint, normalization cuts schema errors. A three-slide deck produced 7 schema errors, and 2 remained after normalization. The remaining 2 concern the order of child elements in the presentation part. Reordering them to match the ECMA standard takes validator errors to 0, but PowerPoint 16.0 could not open that file. The file in its original order opens normally. So we leave those 2 unfixed and drop them from the results, but only when both appear together in exactly that shape. Errors on the same element names also come from other ordering mistakes, so if anything differs, every error is reported.

Excel gets no schema validator. In a side-by-side comparison, that validator caught none of the 9 kinds of defects that mattered. It gave the same diagnosis for a file Excel refuses to open (missing _xlpm.) and for a healthy one, and it flagged every file openpyxl produced for element order in the styles part. A check that can't tell healthy files from broken ones can't be a pass condition, so Excel is judged by in-house checks alone. Dynamic array formulas are an example. openpyxl has no API for writing spill metadata, so the result fills only the top-left cell and the error count is 0. The in-house check catches this.

Hangul documents are built as hwpx. hwpx is not OOXML but an OCF container from the ODF family, with no content-types part and no relationship files. So it has its own validator, and the two share only the shape of a result and the rule "block if fatal." Schema warnings don't block. The published OWPML schema diverges in places from how Hancom Office actually behaves, so the library itself reports schema violations as warnings. Four real hwpx files (2 government forms, 2 internal reports) and newly created documents all produced 0 schema findings.

Only files that won't open are blocked; everything else goes to the model

Problems a validator finds fall into four grades.

Grade Meaning Handling
Fatal The app can't open the file or shows a repair dialog Not delivered
Other-app risk The main app opens it, but other apps such as LibreOffice or Keynote break Delivered, model is told
Silent error It opens with no error, but the content differs from the intent (#NAME?, an array formula that fills only the top-left cell) Delivered, model is told
Tolerated Violates the spec, but every app we checked opens it Logged only

The blocking rule is one question: does the main app open this file? Problems below fatal don't block, because the file opens; each one goes back to the model in the tool result with its grade, location and how to fix it. For a workbook left in manual calculation mode, that means the fact "Excel won't recalculate and shows stale values" plus the instruction to remove the setting. The model that reads the result decides whether to fix it.

Edits are compared with a copy of the original for new problems and lost structure

When the model edits a file someone else provided, the decision is not based on how many problems it has. When we checked 12 internal templates, 9 (6 contracts, 3 presentation templates) had fatal defects before anyone touched them, and one had 411. Blocking on the count would roll back even a perfect edit. So before the edit, a copy of the original goes through the same validator, and only problems that appear after the edit are counted. Problems that were already there don't block, but the model gets a one-line note of how many there were. Without it, the model would take those problems for its own mistakes and fix the wrong things. The note is a summary rather than a list so that hundreds of entries don't fill the context.

Some losses are invisible to a validator. Edit code can delete part of the original's structure and still leave valid XML. In python-docx, the property that sets a cell's text deletes not only the paragraphs in the cell but any table inside it as well. A document that lost two nested tables (17 options) from its value cells this way produced 0 validation findings. A schema says what must be there, not what was there and is now gone. So the structure node counts are compared with the same copy of the original: tables, rows and paragraphs for Word, and slides, paragraphs and text runs for PowerPoint. Character counts are not used, because filling in a template naturally adds text. Excel is not counted either. openpyxl rewrites the whole workbook on save, so even a normal edit would look as if structure had shrunk. Lost structure does not block; the model gets the before and after counts. "Delete this table" can be a legitimate instruction, and the model is the one that knows the instruction.

Rendered pages are attached to the next round without the model asking

A file that passes validation can still ship with overflowing text or overlapping shapes. So when a round (one model call plus running the tool calls in its response) succeeds in writing an Office file, the render sidecar draws the file as page images, and those images are attached after the tool results as input to the next call. The model has already seen the pictures and only decides whether to fix something. This step always runs when the model can read images. How rendering works is in “Model-drawn bars can miss their numbers, so the screen draws charts”.

The number of pages shown has two settings.

Target Default pages Config key
A file the model created in this turn 30 officeReview.createdMaxPages
A file the user provided, when editing or reading it 3 officeReview.maxPages

When only the first 3 pages of a new file are shown, the model says it is done without ever seeing the overflow and overlaps from page 4 on. It just made the file and has the code to fix it, so defects on pages it wasn't shown never get caught. A contract the user provides can run to dozens of pages, and drawing all of them fills the context with images. For edits, pages are compared pixel by pixel with the pre-edit render and only changed pages are shown, with the first few shown before and after side by side.

Each time the same file is written again, it is shown again. In our measurements the model rewrote a file 3.22 times on average, so showing each file only once would usually leave the last result unchecked. The concern about an endless render-and-fix cycle is already covered by the turn's maximum iteration count and time budget.

Design choices

We leave 2 schema errors in PowerPoint files. Aiming for zero validator errors produces files PowerPoint refuses. The cost is that zero validator errors can't be the pass condition, and the 2 we drop are accepted only in their exact shape.

Dropping the schema validator for Excel means the in-house checks carry the decision alone. They don't catch kinds of defects they don't check for, so each newly found defect needs a new check. We judged that better than keeping a check that gives the same answer for healthy and broken files.

The blocking rule is a single question: does the main app open it? The cost is that files that break in other apps, or that have some #NAME? cells, go out as they are unless the model fixes them. Not blocking on hwpx schema warnings follows the same rule. Blocking where the public schema and Hancom Office disagree would stop documents that open fine in Hangul over specification gaps OwlAI did not create.

Edits are judged against the original. A template that already had defects passes the edit, and those defects stay in the output. Whether to ship an edit whose structure shrank is also left to the model.

Always attaching the visual check costs context. Page images grow the input every round, so the page budget goes to new files the model can fix, and files someone else provided are cut to 3 pages.

The validators answer only whether a file opens. Whether the numbers in a table are right is settled by the SQL that queried them. If your agent builds files by writing code, a good place to start is to have the server check that the file exists and opens, and to hand the rendered pages back to the model as pictures.