A1: 'open-sheet READY

The spreadsheet framework built for agents.

Describe the model you need in natural language — your coding agent writes the source. open-sheet owns the cell addressing, the formula references, the recalculation and the export. No A1 address is ever authored by hand.

npx @open-sheet/cli init my-sheets

Agents write excellent analysis and terrible spreadsheets.

The reason is specific: =SUM(B2:B13). Cell addresses are the one thing an agent cannot hold onto. It miscounts the header row. It forgets the total moved when the data grew. It writes a reference to B7, inserts a quarter above it, and now every formula in the file is quietly wrong. Not crashed. Wrong, which is worse.

So open-sheet takes the addresses away. Add a quarter to the data and press F3 — the column definitions below it do not change, and every address in the file moves anyway.

what you write
const quarters = [
  { quarter: 'Q1', revenue: 12_400_000, cogs: 5_100_000 },
  { quarter: 'Q2', revenue: 13_900_000, cogs: 5_600_000 },
  { quarter: 'Q3', revenue: 15_200_000, cogs: 6_050_000 },
  { quarter: 'Q4', revenue: 18_650_000, cogs: 7_100_000 },
]
const quarters = [
  { quarter: 'Q1', revenue: 12_400_000, cogs: 5_100_000 },
  { quarter: 'Q2', revenue: 13_900_000, cogs: 5_600_000 },
  { quarter: 'Q3', revenue: 15_200_000, cogs: 6_050_000 },
  { quarter: 'Q4', revenue: 18_650_000, cogs: 7_100_000 },
  { quarter: 'Q5', revenue: 21_300_000, cogs: 7_900_000 },
]
col('quarter', { header: 'Quarter' }),
col('revenue', {
  header: 'Revenue',
  format: 'currency',
  bar: { color: '#0000aa' },
}),
col('cogs', { header: 'COGS', format: 'currency' }),

col('grossProfit', {
  header: 'Gross profit',
  format: 'currency',
  formula: (r) =>
    sub(r.cell('revenue'), r.cell('cogs')),
}),

col('margin', {
  header: 'Margin',
  format: 'percent',
  scale: ['#aa0000', '#000000', '#00aa00'],
  formula: (r) =>
    div(
      sub(r.cell('revenue'), r.cell('cogs')),
      r.cell('revenue'),
    ),
}),

col('qoq', {
  header: 'QoQ growth',
  format: 'percent',
  highlight: {
    above: 0.15,
    color: '#ffff55',
    bold: true,
  },
  formula: (r) =>
    r.isFirst
      ? null
      : sub(
          div(r.cell('revenue'), r.prev().cell('revenue')),
          1,
        ),
}),
The formulas inside the file, read back out of both builds:
over four quartersover five
total revenueB6 =SUM(B2:B5)B7 =SUM(B2:B6)
last gross profitD5 =B5-C5D6 =B6-C6
last QoQ growthF5 =B5/B4-1F6 =B6/B5-1

The total row moved down one and its range grew. The new quarter’s growth formula points at a row that did not exist a moment ago. Nobody edited a formula.

Gross profit is =B2-C2 in the file, not 7,300,000. Change a number and the whole sheet recalculates — in Excel, in Sheets, in LibreOffice.

Where did this number come from?

The question every spreadsheet you inherit refuses to answer. Click a cell in the viewer and open the inspector: it names the line of source that produced the cell, the formula that reached the file, and whether the number is something you can change or something the model works out.

 INSPECT P&L!B6 
value
12,400,000
formula
— stored, not computed
source
sheets/fy26-budget/index.tsx:39
12_400_000
editable
yes — type a new number here
 INSPECT P&L!G6 
value
2,480,000
formula
=F6*(1-taxRate)
source
— produced by a column rule
editable
no
column "netIncome" is computed, not stored — edit the inputs or the formula

The left cell is an input, so the inspector offers to edit it — and writes the new value back into the data array in the source, not into the grid.

The right one is derived. Note what its formula says: not $B$2 but taxRate, an Excel defined name, so the recipient reads intent rather than coordinates.

Isn’t that what Claude for Excel does?

Different tool, different job. Claude for Excel makes one person faster at making a spreadsheet. open-sheet makes the spreadsheet not need making a second time.

Claude for Excelopen-sheet
Inputa workbook that already existsa source file
In the loopa person, every timea person once, at review
Reviewing a changea binary diffa code review
Doing it 500 times500 sessionsa loop
NeedsExcel, an account, a paid planNode. MIT.

An assistant working inside a spreadsheet still writes =SUM(B2:B13) into a cell. That is a hand-authored address — authored by a model, but hand-authored. Insert a row and it is wrong, and nothing tells you. This is not a question of the model being clever enough: A1 is the only language that medium has.

And when open-sheet cannot compute a cell, it says so. #NOT_EVALUATED, never a plausible-looking number. Anything writing into a live cell produces something number-shaped whether or not it was sure — the failure that costs the most and shows the least.

Where Claude for Excel wins, and it is not close. It writes live formulas too; the difference is never “ours recalculate and theirs do not.” A workbook you did not generate, we cannot read at all. It computes with Excel’s own engine, so its numbers are Excel’s numbers by construction, where we earn that with a cross-engine check on every build. Five hundred-odd functions to our hundred-odd. Pivot tables.

Use that when you have a spreadsheet and want help with it. Use this when the spreadsheet is the output of a pipeline — a monthly board pack, five hundred invoices, a model regenerated whenever the data lands. The two compose, in that order.

What the recipient gets.

a live model
The .xlsx holds formulas, not baked numbers. Change an assumption and it recalculates in whatever they opened it with.
validate
A dropdown instead of a free-text field, carrying both the prompt and the refusal message. A rejection with no reason is worse than no validation.
protect
Unlocks the cells they are meant to fill in and leaves every formula shut. A derived cell is not an input.
appendable
Emits an Excel Table, so a row they add is taken into every range that reads it — and the derived columns fill themselves.
note
Attaches provenance to the cell itself. Shown on hover, not buried in a footnote nobody reads.
print
Headers, footers, page numbers, and page breaks that name blocks rather than row numbers, so they move when the content does.

For your agent.

  1. Scaffold

    npx @open-sheet/cli init my-sheets writes a workspace carrying five skills — how to author, how blocks are placed, how formats work, what the recipient can do with the file, and how it prints.

  2. Ask

    /create-sheet and describe the model. The skill opens by confirming the material and refusing to invent numbers, which is the failure this medium punishes hardest.

  3. Inspect

    Click any cell in the viewer for its source line, its resolved formula and its computed value — or leave a comment for the agent to apply.

  4. Automate

    An MCP server over Streamable HTTP exposes the same operations the browser uses, so a stale write is refused with a 409 rather than silently overwriting somebody.

 VERIFY 

Checked against a real spreadsheet, every build.

Our evaluator and Excel have to agree about what a formula means, or the number in the viewer is a guess. Every whitelisted function is compiled into a fixture, exported without cached results, recalculated by LibreOffice, and diffed against what we computed. A function joins the whitelist only when a second engine agrees.

That check is also why SORT is implemented here rather than taken from a library: the library’s compares as strings, which is indistinguishable from correct until your numbers stop having the same number of digits.

pnpm test formula/verify
138 cases: 138 agree, 0 disagree, 0 not evaluated, 0 engine failed

127 functions are whitelisted, and that line is printed by the run that produced this page — not remembered from a run that passed once. A disagreement fails the build; a function the engine cannot compute is reported as a gap, which is a different thing and must never read as agreement.

npx @open-sheet/cli init my-sheets