Skip to main content
A saved formula belongs to its Variable. When the user selects a visible row, column, or cell, the saved Table block gives the target that resolves the right Variable and scope. Every model formula is written through edit_table_blocks.

Shape a call as one action block

edit_table_blocks has seven actions: create, update, reshape, copy, delete, set_time_rollup, and update_calendar. Fill exactly one block named for the action, and put that action’s fields inside it.
  • scenario sits beside the block and applies to it. It is also accepted inside the block; a call that names two different Scenarios in the two places is refused.
  • A field the chosen action does not take is refused, with accepted_fields listing that action’s own fields plus scenario.
MCP tools and call shapes lists every shape refusal.

Write through the saved table

First read the table with inspect_table_blocks to get its visible row and column references:
Then send the related formula edits together in one update, with the same scenario as the read, so both resolve the same saved table:
  • A row with no Date scope takes formula. A row that runs across time takes default_formulas, with an actuals entry, a forecast entry, or both. An entry you leave out stays unchanged.
  • clear_formula: true in place of formula removes the formula the row owns for that range.
  • The server derives the Variable, the cell address, the formula range, and the write permissions from the visible target. Copy references from the inspection result, such as Gross Profit#3f2, instead of inventing IDs, positions, or range identifiers.

Tables that save a comparison

A table that saves a Scenario or period comparison takes writes too. Name the selected Scenario’s own column, with or without the comparison qualifier the read printed, and the write lands in that Scenario. A compared Scenario’s column, a compared period’s column, and a difference column take no write: the refusal says the table’s saved comparison is left out, because those columns show values saved in another Scenario or period, or calculated.

Formulas the items beneath a row inherit

child_defaults on a row_updates or column_updates entry holds the formulas the cells beneath the row inherit, one entry per breakdown Dimension at any depth:
  • Each entry names its dimension and carries actuals, forecast, or, on a timeless table, formula. One row_updates entry can set the row’s own formula and its children’s together.
  • A deeper level’s entry is saved on the level above it. In Total by Owner by Deal, the Deal entry is the formula on Total[Owner].
  • An axis row, such as Total[Stage], sets the formula for every item’s children; an item row, such as Total[Discovery], sets it for that item only.
  • clear: true on an entry removes an owned timeless child formula; dated ranges clear through actuals or forecast. The item’s own clear_formula removes only the parent row’s formula.

Create a visible derived row

A new Variable can be defined and placed on an existing saved table in the same change. structure.insert names the side, the new Variable, and its formula:
structure inserts, then moves, then removes rows and columns. A removal drops a row or column and its subtree from this table, never the objects themselves, and every Variable and Dimension must keep a row or column on at least one table. The same update can write the cells of inserted rows with cell_updates or values. To start a new table instead, use create with its complete rows and columns:
The saved row makes the calculation visible, inspectable, and available for follow-up. A Variable row placed beneath a Dimension row saves its default_formulas open over that Dimension, so every item calculates the formula, under the same condition a later row_updates on that row writes.

Create several dependent tables in one call

When a build needs more than one table, and a later table’s formulas name Variables an earlier table declares, put the tables in a tables list inside one create instead of making one call per table:
  • tables holds up to 20 tables. They save in list order in one change, so a formula in the second table can name a Variable the first table declares.
  • Two entries that name the same new page share it.
  • Each entry carries its own name, page, rows, columns, and settings. scenario and dry_run sit on the request and apply to every entry.
  • The response holds one receipt per table, in list order, and every table shares one change_log_id. A refusal of any table refuses them all and names the table.

A new dated row gets a time rollup

A formula on a month row governs month cells only. Quarter, half-year, and year cells read the Variable’s time rollup.
  • A create or insert row may name rollup: SUM, LAST, or AVERAGE.
  • When it names none and the table’s periods are at the Model’s base grain, the save writes the rollup the request implies, at every grain coarser than the base, for Actuals and Forecast: SUM for a number with no aggregation or with SUM or COUNT, and LAST for aggregation_function: LAST. It writes none for a timeless formula, format: PERCENTAGE, a value that is not a number, or any other aggregation.
  • A row created on a table whose periods are quarters or years, or on a table without Date, gets no implied rollup.
  • The receipt states the implied rollups in one default_rollup issue, and does not list them as rollups or count them as default_formulas.
  • A ratio row created without format: PERCENTAGE gets SUM, so its year cell adds the monthly ratios. set_time_rollup, or a named rollup, corrects it.
A default_formulas formula written through a table whose periods are coarser than the base grain, such as a yearly table in a monthly Model, is saved at that coarser grain, and the receipt warns: the year cells compute the formula whatever their month cells hold, and the month cells do not read it. Write the formula on a row of a base-grain table, or in a variables entry, when it should govern the base periods.

A breakdown’s totals are written for you

When a table breaks a Variable down, child_defaults on the deepest axis row is the formula for the lines, saved on each line’s full path. The save then gives every coarser level the tables show a default that sums the level beneath it, one ordinary formula per level, and the receipt lists them: “Revenue’s level defaults, which add up its [Customer, Line, Product] lines at each level the tables show: saved Revenue[Customer, Product], Revenue[Product] and Revenue.”
  • A value set on any row reaches every total above it.
  • A formula an author writes at a level replaces the default there, and a default edited into other text stays with its author.
  • The sums exist only while a line formula claims the lines. Clearing the line formula removes the sums left in its range.
  • A Variable with format: PERCENTAGE gets no sums and no 0 on its lines. Each level above them gets the line formula or NULL, and the receipt states which: “saved the line formula on [rows], so each of those rows computes that formula from its own inputs”, or “saved NULL on [rows], which reads blank, where the lines hold typed-in numbers or several formulas, or where their formula takes its own row’s item of a dimension that row lacks or reads a variable with no value on that row”.
  • A row’s numberFormat is display only and changes no formula.
  • Each sum adds up the one shown level beneath it, never every line dimension at once. A total above lines whose formula has no Date is saved without Date too.
  • A sum a person or Ari saved at a level is left alone, even when it is spelled exactly as the sync spells its own sums. The sync replaces or removes only the formulas it wrote itself, which a variable read marks writer: "level_default_sync".
  • When one call creates several tables, each table’s receipt lists the level defaults for every Variable that table shows, including a Variable a row names only by reference.
  • A create dry run runs the same totals step and rolls it back, so its model_effects carry the statements the committed receipt puts under level_defaults, for each table in the call.
Totals, aggregation, and time rollups explains why totals work this way.

Add items and write their values in one call

  • An update adds the items that dimensions[].add_items or replace_items names before it resolves values and cell_updates, so one call can add E9 to Entity and write ["E9", 42] in values.
  • A create, or an update that adds rows, can also fill a mapping column on the rows it adds. A Department cell on a new employee row saves the one quoted item, "Sales"; a new item joins the Dimension’s list; and a value that is not one item is refused before anything saves, naming the cell.
  • Cells of one lookup save in one mapping write, so filling 400 Departments costs about the same as filling one.
When a write changes a shared mapping row, the receipt’s shared_mapping_changed notice names the first changed row and its item before and after, and counts the rest: “The shared mapping changed: EMP-001’s Department moved from Engineering to Operations, and 3 more rows changed. Other tables and periods that use it can also change.” A first assignment reads “is now Sales”, and a cleared cell “was cleared; it was Sales”. Every table that reads the mapping recalculates in the background after the call returns; inspect_dimension_mappings lists those tables.

Where a timeless formula is written

formula and clear_formula write a Variable’s timeless formula, which applies in every period, Actuals and Forecast alike. default_formulas.actuals and default_formulas.forecast write formulas limited to Actuals or to Forecast, and create and update both accept them.
  • A create writes a timeless formula for a Variable it makes, including on a row of a table with Date columns. It refuses one on a row that places an existing Variable.
  • An update writes a timeless formula on an undated row or column, and refuses one on a dated row or column.
  • Both refusals use the same sentence, which says where each kind of formula is written.
A child_defaults clear on create goes through the same planner an update uses, so a child that owns no formula reports unchanged.

An axis that only lists items has no formula to edit

formula and clear_formula edit the formula of a calculated value: a Variable, a Dimension shown as a value through its mapping or formula, or the Variable that encloses a Dimension breakdown. An axis that only lists a Dimension’s items has no calculated value, so a formula edit aimed at it is refused with a reason instead of a resolution error. The reason:
  • opens “this table axis shows its dimension with role DIMENSION: it lists the dimension’s members, and no calculated value is on either axis”;
  • says that dimensions add_items, remove_items, replace_items, and rename_items edit saved items, and that a retained breakdown edits its enclosing Variable’s formula;
  • adds that “a dimension axis whose role is VARIABLE holds one value of that dimension for each member on the other axis, and a formula on it saves the rule that calculates that value”.
The role field’s description and the item-condition refusal carry that same sentence. A formula that generates a source-linked Dimension’s items has no write in the tool: the person writes it in a table, from the Dimension’s label-row menu, with Edit dimension formula.

Save a Variable’s formula without a table

When the requested formula has no visible table target, name the Variable in a variables entry. No table target is needed:
  • The formula is saved on the Variable with one Date term at the Model’s base grain, the same address a plain row on a base-grain table writes. A later row_updates write on a table that shows the Variable edits this formula instead of adding a rival.
  • The same entry takes the Variable’s settings, wherever it is read: name, data_type, aggregation_function, preferred_time_property, format, decimal_places, currency, and variance_colors.
  • Item edits on a Dimension still name a table that shows it.

Filter the items an axis shows

members on a row_updates or column_updates entry is the saved item filter of the selected axis. It takes one of two forms. A list of item names replaces the filter, including a saved condition. [] clears it.
  • The list may name empty:None, the rows with no value.
  • Each name is checked against the Dimension’s hand-added items and the source’s item text, then, for a name those do not hold, against the calculation’s own item list. So an item a formula or mapping computes is saved, and so is a number written as the table’s rows write it, such as 13840.0. Date and number items match by their saved text, such as "2026-01-01 00:00:00+00".
  • A name the calculation does not hold either is reported under unmatched. A list that names no item at all is refused with the Dimension’s first items.
  • Only an item filter may name empty:None. add_items, replace_items, rename_items, and a mapped item named empty:None are refused with “empty:None is the spelling of a dimension’s empty member, which holds the rows with no value and exists without being added”.
A string is a live condition on the Dimension, evaluated over its items at every calculation to narrow the saved list, so an item the saved list already leaves out stays out. A table read prints a saved condition in this grammar with qualified references, such as [`Employee ID`#403 where `Employee ID`#403.Department#2f5 = "Engineering"], so it can be written back unchanged, and a list read names the home of a Dimension the condition reads from outside the table under property_homes. When the condition’s text cannot be printed, the read reports {"saved": true, "shown": false, "reason": ...} instead. The reason is it reads a variable or dimension this read cannot name for this person for a property you cannot read, or it reads a variable or dimension that is not in this scenario for one the Scenario no longer holds, such as a deleted Dimension; when both apply, the permission reason is given. Only the list, definitions, and grid reads can tell the two apart, so an edit receipt gives the permission reason for either.
  • Literals are quoted strings. A bare number is a reference offset, not an item.
  • Region not in {"West"} keeps every named item but West. The empty item has no value, so a comparison on it is unknown; a null check inside where keeps it: Region where (Region = null or not (Region in {"West"})).
  • A condition may read a Dimension mapped from the axis, such as Employee where Status = "Active". A condition on a Dimension the axis is not mapped from is refused, and the refusal names the step that makes it readable: edit_dimension_mappings maps that Dimension from the axis, which gives each axis item one item.
  • A condition never reads a Variable. Vendor where FutureSpend > 0 is refused with “a member condition on Vendor reads only Vendor”, and the refusal names the step that works: place a Dimension as a value column on a table whose rows are the axis, give it a formula that reads the Variable, such as if(FutureSpend > 0, "Yes", "No"), and save that formula with edit_table_blocks. The Dimension then has one value per item, and Vendor where HasFutureSpend = "Yes" stays current after the next import.
An item row such as Total[West] names its axis. hidden on the same entry hides or shows the saved axis the selection belongs to, so hidden: true on an item row hides every item of that level. When a receipt that lists every item would exceed the result allowance, each member_filters entry reports members_omitted, the count, in place of its members list, and the edit still answers outcome: applied.

Edit an address that holds several saved formulas

A table edit names an address: the Variable, its condition, and its formula range. Imports and older writes can leave more than one saved formula at one address.
  • An exact $[...] formula outranks an inheriting [...] formula at its grain, and among formulas of the same form the newest save wins. So cells read one formula: the newest exact one when one exists, otherwise the newest inheriting one.
  • A row_updates, child_defaults, or cell_updates write at such an address edits the formula the cells read, and deletes the older copies of the same form in the same change. The receipt names each deleted copy: “This write also deleted an older copy of a Forecast rule that Advertising carried at this address: 1 at [Vendor in any, Date.Month in any]. No cell read a deleted copy, so no value changed.”
  • An exact formula and an inheriting [...] formula at the same grain are not copies. The write edits the exact one, keeps the inheriting one, and names it in the receipt.
A table read marks a default that has older copies with the duplicate_default_formula issue, which counts the copies and says that a write to the default deletes them. The default itself shows as defined here and can be written.

Set where a source’s rows sit on the calendar

Where an imported source’s rows sit on the calendar is one fact about the source, and a sources entry on update or create states it once:
  • target is the query reference a read prints under sources, and preferred_time_property is one of the source’s date columns.
  • The write saves that column on every Variable and Dimension of the source in the same change. A variables or dimensions entry in the same request that sets its own preferred_time_property wins for that object.
  • A column that is not a date column of the source is refused, naming the columns that are.
  • An update that carries only sources needs no table target.

Declare a mapping in the create that reads it

A create whose formulas read a mapped Dimension can declare the mapping in the same call, instead of calling edit_dimension_mappings first. The create’s dimensions entry takes target, keys, items, and catch_all, as edit_dimension_mappings map takes them: A table formula or mapping write that would give a Dimension a second lookup is refused, and nothing saves, when the two lookups share no key and both reach every row of the same imported table, because the calculation could not choose between them for a row both match. Lookups that share a key, such as Account alone and Account with Account ID, are not refused: the more specific one wins and the other is its fallback, as Dimension mappings describes. The refusal names the Dimension and both lookups: target dimension "Direct Cash Flow Group" already has a mapping input keyed by [CF Classification Basis] that reaches the same imported source as the proposed input keyed by [CF Line ID]. Both inputs would match every source row, so "Direct Cash Flow Group" would have no defined mapping result. A key that is itself a mapped Dimension counts through the import it reaches.
  • The mapping is written in the same change, before the rows, so a formula such as sum(`Income Statement Amount`[`P&L Line` = "Revenue"]) resolves in this call. The table, the Dimension, its items, and its rows commit together.
  • items maps single source values to fixed items, so a nonempty items needs exactly one key.
  • A mapping formula’s condition is saved as a relative [...] formula; a leading $ before a nonempty condition is dropped. So when one lookup is keyed by Account and another by Account together with Account ID, a row the two-key lookup has no formula for falls back to the one-key formula instead of the catch-all.
  • The catch-all [] is unchanged, and a formula saved as $[...] earlier keeps its exact condition until a mapping write names its row again.
  • The receipt’s items entry for the Dimension carries the mapping’s facts: keys, rows_created, rows_updated, target_items, and catch_all.
  • A refusal on either side saves nothing.
Dimension mappings and shared Dimensions explains how mappings work.

Set a table’s window and grain

window is the table’s own date range and grain.
  • On a create, it holds start, end, and granularity. A dated table names its grain, such as MONTH; a timeless table names NO_GRANULARITY, as the tables example above does.
  • On an update, the range is {"type": "ABSOLUTE", "start": …, "end": …}, and {"inherit": true} clears the table’s own window.
  • start and end accept a plain calendar date, such as 2026-02-01, as well as a full timestamp, and create and update save the same window for either form.
  • update with grain moves the table’s Date columns to that grain, so a monthly table asked for QUARTER shows quarter columns, and the receipt reports the moved Date column. A table whose Date header shows two grains at once, such as Year above Month, is refused a new grain.
  • grain on a row or column node sets the display grain of a datetime axis only. On a Variable row it is refused, and the refusal states where a Variable’s grain comes from, such as “grain on a row or column sets the display grain of a datetime axis; “Revenue” has data type FLOAT. A variable’s default_formulas save at the table’s Date grain: the grain of its Date axis, which time sets on create and grain sets on update or reshape.” That sentence describes a formula written through a table; Save a Variable’s formula without a table states the grain a variables entry saves at.

Write values into dated columns

  • A values write names each Date column by the start of its period at the table’s grain. On a table with yearly columns, 2024-01, 2024, FY2024, and 2024-01-01 all write the 2024 cell; 2024-02 is refused as a date inside a YEAR period, and the refusal names the form that works.
  • A period outside the table’s window is refused with the window and the start or end that would hold it, such as “2025-12 is before this table’s window 2026-01…2026-12; a window that starts 2025-12-01 holds it”. A call may set window and write the new periods together.
  • A whole number such as 2026 also selects a number item printed 2026.0, and a row renamed in the same request resolves under either name.
  • A JSON number in exponent form saves as the same value in plain decimal digits, so 1.5e-3 saves as 0.0015; the formula grammar has no exponent literal. A number outside the 64-bit floating-point range is refused with “<literal> is outside the 64-bit floating-point range”.
  • Dated inputs in values or cell_updates, and the periods a readback selects, leave the table’s saved display window unchanged. A report that must show a requested start and end needs window set on the same call, or the table keeps showing its old range with the new months outside it. A readback of selected months proves those cells, not the displayed range.

Write in a Scenario

scenario defaults to the Scenario in view, which can change between turns. Through MCP, that is the Scenario _context.layerId names, or Main. When a call that saved something omits scenario, each table receipt carries a scenario_warning issue that says which Scenario the write used:
  • With a Scenario in view: “No scenario was named, so this call used Breakeven Plan#4c1, the scenario the person is viewing. The scenario in view can change between turns.”
  • With none in view: “No scenario was named and none is in view, so this call used Main.”
A refusal, a dry run, or a call that saved nothing carries no such sentence. A Scenario stores only what differs from the Scenario it branched from. A cell write in a child Scenario that repeats the value the cell already reads from its parent stores nothing: the edit’s outcome is unchanged, and a formula_warning says the Scenario already reads this value from its parent, so the cell keeps following the parent and changes when the parent changes. A different value is saved as the Scenario’s own and holds through the parent’s later edits. Scenarios, Snapshots, and version history explains why.

Delete a Variable or Dimension

  • delete with a target alone removes the whole table, and deletes only the Variables and Dimensions that no other saved table still shows. An object another table shows stays there, with its formulas, values, and items.
  • No table owns an object. A row_updates or reshape write may take an object off the table it was created on while another table shows it; only removing its last occurrence needs it placed elsewhere first, or deleted outright.
  • delete with a variables or dimensions list removes only the listed objects, wherever they are placed. An object on another table, or on no table, is reached by name; the named table only scopes the call. A name that matches no readable object is refused with “delete target … matches no readable variable or dimension”.
  • When other formulas, blocks, or settings still read one of the objects, the delete is refused and lists what reads it under where each object sits, such as “is shown on table Revenue” or “has no recorded table location”, ending “With force, the object is deleted and these references break.” force: true accepts that those references break.
  • force on the delete covers every listed entry; force on one entry covers that entry alone, so a call can force some entries and not others. update entries do not take force.
  • A refused table delete opens with “Nothing was deleted” and the count of dependents. A completed one carries a links_end issue, because links to the table in earlier chat messages no longer open it, and a table rebuilt in its place has a different identity.
  • Removing a Dimension item is refused, opening with “Nothing was removed”, while a formula, a drill-in, a chart or Canvas condition, a row on another table, or a filter that lists only that item names it. Manual sort orders, and filters that still list other items, drop the item instead.

A formula belongs to its Variable

A create row that names an existing Variable by its plain name places that Variable. Rows match names without regard to case, so Deferred revenue places Deferred Revenue. A plain name adds to the Variable only where it holds nothing:
  • a formula for a range or level that has none,
  • a value at a cell that has none and that no formula of the Variable calculates, or
  • a type or format that was not set.
A request that would replace what the Variable holds is refused before anything saves, for every table that shows it. When judging that:
  • A formula calculates the cell unless it is pinned to one cell. An exact condition with an open term, such as $[Date.Month in any], is a formula over every month.
  • A dated formula counts at the Model’s base grain and at the grain of the value given, so a formula saved from a quarterly table refuses a quarterly value.
  • The generated formulas a breakdown’s save recorded as its own are judged by what they compute. The 0 on the lines supplies what a blank cell reads, so a value on a line is an addition; the sum on a level calculates that level alone, so a value on the Variable’s own row is a replacement.
  • A value on a timeless Variable that holds nothing is an addition.
  • Every aggregation that is set, DO_NOT_AGGREGATE included, is a setting a plain name does not replace.
  • The check does not read which items a condition names, so a formula for one item refuses a value on another item’s cell.
The refusal names the two ways forward:
  • The Variable’s reference, such as Budget#3f9, writes that Variable and replaces what it holds.
  • new: true on the row creates a separate Variable with the same name and leaves the existing one as it is. Throughout a request with new: true, in rows, columns, formulas, values, variables entries, and later tables of the same create, the plain name means the new Variable, and each existing namesake answers only to its reference.
The receipt’s items entry for a reused object carries:
  • home, the table it was first created on, when that table exists and is readable;
  • added, with the rules, values, and settings the save gave it;
  • beside_rules, the count of formulas the Variable already held that calculate its cells, which is 0 whenever values were added;
  • rule: "replaced" when a write by reference replaced a formula the object already had, in Main or in the Scenario being edited;
  • and, on an update or reshape entry for a reused Variable, level_defaults, the sums and 0 the save’s breakdown added to it or removed from it.
Giving an existing Variable new default_formulas by its reference, such as Revenue#3f2, while placing it on another table rewrites the formula every table reads. When the formula it replaced was a formula, the receipt names it and the address the Variable carried it at. Writing a value over a plain number, quoted text, a date, or null states no replaced formula; the committed edit’s previousText carries the old value instead. Placing a Variable without new defaults leaves its existing formula alone.

Names and references

  • A stale table reference whose name no longer exists resolves by its suffix when the suffix has at least three hex digits and exactly one table carries it. inspect_table_blocks list reports the move in a renamed note, such as “Vendors#083: no table has that name; its suffix matches the table now named “Anomalo Vendors"".
  • Formula names match case exactly. A formula that names `Deferred revenue` when only Deferred Revenue exists is refused with the object it matches: “`Deferred revenue` matches the existing `Deferred Revenue` (names match without case); formula names match case exactly, …”.
  • A reference field and formula text accept the same written form of a namesake. The short suffix a readback prints, such as Currency#3 beside Currency#8, names the object it prints for, and a suffix of three or more hex digits is checked against the object’s retained identity, so a reference issued for a deleted object never moves to a new namesake.
  • A backticked name that no object carries, such as `Amount#3c2`, reads as the reference it quotes. In a table row or column, backticks still mark a literal new name.

Check before you save

A table write is atomic: the whole change saves, or none of it does. Only create takes dry_run: true, which validates a new table without writing; edits to saved tables and deletions take no dry run. Validate and verify formula writes has the details.

Read back what you saved

  • Ask for a readback in the same write with readback.rows and readback.columns. rows alone reads every column, columns alone every top-level row, and a renamed Variable or Dimension resolves under either name. readback.depth adds child levels: 1 adds each row’s items, 2 theirs, and all every level; omitted reads the selected rows alone. readback.also reads up to 3 other saved tables at the same change, each entry {"target", "rows", "columns", "depth"} with the readback’s own meanings: omitted rows reads every top-level row, omitted columns every column, and depth nests the same way. Each entry’s result comes back under readback_also in request order.
  • An update, reshape, or delete may carry based_on: the as_of of the table read, or the change_log_id of the table write, it builds on. The edit saves either way. When someone else saved to the table after that point, the receipt adds a changed_since_read issue naming each save and who made it: a person by name, “a person in the app”, “Ari in another chat”, or “an automated process”, up to five saves and then a count.
  • Check the intended Scenario, representative dates and segments, formula origins, and the actual numbers. When the receipt carries warnings, read them before moving on. Validate and verify formula writes lists them.