Technical document for the SpreadsheetTool plugin: how it works, the area-receipt technique every cell-writing method must follow, and the project rule that keeps it consistent for future methods.
SpreadsheetTool is a plugin (implements AIOrchestrator.API.IAgentTool) that exposes
spreadsheet operations to LLM agents. The public methods become LLM-callable tools via
the GeToolDefinitions reflection pipeline, which reads the XML documentation file.
The implementation is built on the Aspose.Cells_FOSS fork (MIT). The fork has no calculation engine, no auto-fit API and no rendering: everything the agent needs that the fork cannot do is provided deterministically by the tool itself (see "Deterministic assists" below).
The agent works on a sandbox: the tool receives agent-facing paths
(/folder/file.xlsx) and resolves them with SandboxPath.Resolve to host paths. Every
result that names a file returns the sandbox-relative path (SandboxPath.ToAgent),
never a bare file name and never a host path — the agent needs it to attach the file via
its done method.
The write methods (set_cell_value, set_range, append_rows, set_cell_formula,
apply_style, format_header_row, set_page_setup) used to answer "true". That is a
zero-information result: the agent must trust its own memory of what it sent. When
that memory drifts, the agent silently overwrites cells, skips rows or duplicates whole
blocks without ever noticing — and the done message may then claim things that were
never written.
Every method that creates or modifies cells returns an area receipt: a compact,
deterministic snapshot of the exact area it touched, produced by DescribeArea:
{
"sheet": "Coffee Shop Data",
"range": "A1:C8",
"rows": 8,
"columns": 3,
"values": [["Month", "Revenue ($)", "Costs ($)"], ["January", "8500", "6200"], ...],
"formulas": { "B8": "=SUM(B2:B7)" },
"types": { "A1": "text" },
"formats": { "B2": "$#,##0.00" }
}sheet— the worksheet name (enables a working memory across multiple sheets);range— the exact A1 position on the page of the modified area;rows/columns— the area dimensions;values— the display values (number formats applied), so the agent sees what a human would see;formulas/types/formats— SPARSE sections that list ONLY the cells that carry that information. Cells not listed intypesare numeric. Empty sections are omitted entirely, keeping the token cost low.
- Feedback / memory of work: after every write the agent has ground truth about what
was actually stored (including formatted values and formulas), instead of
"true". - Self-verification: the receipt lets the agent check for missing rows, duplicates or overwrites at the moment they happen — without a separate read round-trip.
- Deterministic, zero extra LLM cost: the receipt is built from the in-memory workbook by the tool itself; it costs only the few tokens of the JSON.
- Every method that writes cells or applies settings to cells returns the area
receipt of the modified area (single cell, block, styled range, header row, page
setup), or an
"Error: ..."string on invalid input — never a bare"true". - Read methods may use the same shape:
GetRange(detailed=true)returns the same sparse JSON for an arbitrary area, so the agent can "zoom into" any region. - The XML
<returns>documentation of every write method states explicitly that the result is the receipt/feedback for the agent to verify its own work.
Every future method of this tool that works on cells MUST return the area receipt of the area it modified — via
DescribeArea(...)— exactly like the existing write methods. It must NEVER return a bare"true", an empty string, or a plain count.
- Call
DescribeArea(sheet, startRow, startCol, endRow, endCol)on the modified range and return its result (or a dictionary withsheet+range+applied:[...]for settings-only methods likeapply_style/set_page_setup).- Keep the sections sparse: include
formulas/types/formatsonly when at least one cell carries them; omit empty sections.- The receipt must identify the position on the page (
rangein A1) and thesheetname, so the agent can maintain a working memory across sheets.- Document the contract in the method's XML
<returns>.This preserves the feedback loop the tool provides to agents and prevents silent write errors from ever becoming invisible again.
At every persist (Save, SaveAs, Dispose auto-save) the tool applies a
deterministic, agent-invisible auto-format pass before validating and committing the
file:
- bestFit columns: columns the agent touched that still carry the default width are
written with
bestFit="1"and NO width value, so the OPENING APPLICATION auto-fits them (formula results included — the fork cannot compute them). User-set widths are never touched (the user is always right). - Table titles: text cells forming a contiguous vertical run directly above a number/formula cell are styled bold + light pastel background, one palette color per table. Explicit user/agent styles are never overridden.
- Format normalization: agent-supplied number formats with surrounding quotes
(
"$#,##0.00") are normalized — a quoted string would render as literal text in Excel format syntax.
The pass runs on a temp file, the XML parts are parsed for well-formedness, and only a valid result replaces the real file (the on-disk workbook is never replaced by an invalid one).
- The workbook is saved through
PersistValidated(temp → validate → atomic replace), then a git snapshot is taken withGitSupport.SnapshotforGitTool.restorerollback. Dispose()auto-saves pending changes (the agent may forget to callsave) — the delivery of the file to the user must never see a stale/empty on-disk version.- Every create/save result returns the sandbox-relative path so the agent can
attach the file via its
doneattachmentsfield.
The complete method catalog is generated by reflection from the public methods + the XML docs. Verify the surface with the harness:
dotnet run --project SpreadsheetTool.Tests -- --methods
dotnet run --project SpreadsheetTool.Tests -- --autofmt # deterministic auto-format + receipts
dotnet run --project SpreadsheetTool.Tests -- --charttest # chart caches + bestFit regression