Editing a Notebook Workbook: Code Sheet & AI Handshake

Hidden sheet layout, the AI edit loop, and the H1/H2 request-response handshake.

This section documents the internal structure of the hidden _BOARDFLARE worksheet and the protocol AI agents must use to inspect, modify, and verify notebook code.

The Hidden Code Sheet (_BOARDFLARE)

Boardflare stores the complete Marimo notebook directly within the Excel workbook on a VeryHidden worksheet named _BOARDFLARE.

Storing the notebook in worksheet cells ensures that: - Any tool (Office.js in chat assistants, or external Python scripts using openpyxl) can inspect and update code. - Notebook code survives workbook operations that strip Custom XML parts.

Sheet Layout

Range / Cell Content Description
A1 Instructions AI edit instructions. They point at the llms.txt and llms-full.txt of the notebook runtime the workbook executes from (production, or the preview or local runtime a development add-in uses).
A2 (Blank) Empty row separating instructions from notebook cells.
A3:A<last> Notebook Cells Column A holds only real notebook cells, one cell per row. Empty rows are skipped. Formatted as text (@).
G1 / H1 Request Request counter. Incremented by AI agents as the final step after modifying code.
G2 / H2 Response Execution response JSON written by the add-in after reloading the notebook.
G3 / H3 Revision Monotonically increasing workbook notebook revision counter.
G4 / H4 Startup mode Opening mode: "edit" or "run" (App mode). Formatted as text (@).
G5 / H5 Format version Layout version, value "1". Formatted as text (@).
G6 / H6 Saved at ISO 8601 timestamp of last workbook save. Formatted as text (@).
G7 / H7 Source SHA-256 Lowercase hex SHA-256 digest of the canonical joined source. Formatted as text (@).
G8 / H8 Engine Always "python". Formatted as text (@).
G9 / H9 Notebook header The notebook header verbatim: import marimo and app = marimo.App(...). Formatted as text (@).
G10 / H10 Template ID Optional template identifier (e.g. "clean-data"). Formatted as text (@).
G11 / H11 Template version Optional template release version (e.g. "rt-..."). Formatted as text (@).

Ranges A3:A1048576 and H4:H11 are explicitly formatted as text (@) so code and metadata are never reinterpreted by Excel as numbers, formulas, or dates.

Important Formatting Rules

  1. Column A holds only cells: Every row from A3 down must be a valid Marimo code unit (e.g. @app.cell, @app.function, or with app.setup).
  2. Header lives in H9: Never place header code (import marimo, # /// script, app = marimo.App(...)) in column A. It belongs in H9.
  3. Closing boilerplate is never stored: Never write if __name__ == "__main__": app.run(). The add-in automatically appends the run guard when reassembling the notebook.
  4. Cell character limits: Excel cells store at most 32,767 UTF-16 characters. Keep each cell under 32,000 characters. If a cell is too large, split it into multiple Marimo cells.
  5. Setup cell: with app.setup must appear in at most one cell. If present, it will be placed immediately after the header during reassembly.
  6. Do not modify metadata: Do not edit rows G3:H8 or G10:H11, which the add-in maintains automatically.

Hand-Edited Layouts

Readers tolerate rows written by hand, and the next save rewrites the sheet in canonical order: - Blank lines or extra newlines around a row’s code are ignored, so inserting a row can never glue two cells together. Empty rows are skipped. - A stray run guard row (if __name__ == "__main__": app.run()) is skipped. - A with app.setup row that is not first is moved to first.

Other layouts are rejected with a LayoutError that names the row, for example {"cell": "A7", "type": "LayoutError", ...} in the response JSON (H2). The notebook is not loaded and the sheet is left as it is: a save over it is refused, and a reset still works. - A row that looks like the notebook header (import marimo, __generated_with, # /// script, app = marimo.App). - More than one with app.setup row. - Cells in column A while H9 is empty. - A run guard inside H9.

Storage rules: - A cell that exceeds the limit rejects the save, naming the cell and asking you to split it. Nothing is truncated. - Control characters that XML 1.0 cannot carry (such as \x00 or lone surrogates) are rejected. - H7 records the source’s digest as last written by Boardflare, so Boardflare can tell the cells were edited outside it. A mismatch is not an error: the cells are authoritative. - A sheet whose format version in H5 is anything other than "1" (an empty H5 counts as "1") is reported as unsupported_schema and never overwritten. - A1 is rewritten whenever it differs, so an open workbook points at the runtime of the add-in that opens it. - The add-in creates the code sheet on the first save and repairs it on start. - Boardflare ignores its own writes to the sheet. In Excel, any other edit to A1:H1048576 (an AI assistant, another user, undo) is noticed shortly afterwards. With no unsaved edits in the pane, the notebook reloads and the pane says so. With unsaved edits, the pane asks whether to reload from the workbook or keep editing. Saving over a changed sheet raises the Notebook changed outside the editor choice. Incrementing the request counter in H1 also reloads the notebook.

The AI Edit Loop Handshake

When an AI assistant (such as ChatGPT, Copilot, or Claude inside Excel) modifies a workbook, it communicates with the Boardflare add-in via the request counter (H1) and the response JSON (H2):

AI Agent                                         Boardflare Add-in
   │                                                     │
   ├─ 1. Edit code rows in Column A / H9                │
   ├─ 2. Increment H1 (e.g. 0 -> 1)                      │
   │                                                     ├─ 3. Detects request change
   │                                                     ├─ 4. Sets H2 status="running"
   │                                                     ├─ 5. Reloads and runs notebook
   │                                                     ├─ 6. Writes final response JSON to H2
   │◄─ 7. Polls H2 until status!="running" ──────────────┤
   │                                                     │
   ▼                                                     ▼
Verify status: "ok" or fix errors

Handshake Protocol Steps

  1. Diagnose First: When a user reports an issue, do not blindly edit code. First, add 1 to the integer in _BOARDFLARE!H1 and read _BOARDFLARE!H2 to inspect any existing errors and traceback.
  2. Apply Edits:
    • To update an existing cell: update the corresponding row in Column A (A3:A<last>).
    • To add a new cell: append it to the next empty row in Column A.
    • To change notebook width or app settings: update the header text in cell H9.
  3. Signal Reload: As your last action, increment the integer in _BOARDFLARE!H1 by 1.
  4. Wait for Settlement:
    • Read _BOARDFLARE!H2. Because Office.js environments may lack setTimeout, poll H2 with short successive reads until request equals the integer in H1 and status is not "running". Allow up to the AI edit settle timeout in System Limits.
  5. Evaluate Response:
    • status: "ok": The notebook compiled and ran all cells without uncaught Python exceptions.
    • status: "errors": One or more cells failed. Inspect the errors array in the JSON response, address the issue, and repeat. Stop after at most two fix attempts.

Response JSON Schema (cell H2)

Cell H2 contains a JSON string with the following fields:

{
  "request": 1,
  "status": "ok",
  "time": "2026-10-02T21:00:00.000Z",
  "errors": [
    {
      "cell": "A5",
      "type": "ValueError",
      "message": "Unsupported driver: Units sold",
      "traceback": "Traceback (most recent call last):\n..."
    }
  ],
  "note": "All cells ran without errors."
}
  • request (integer): The request counter value (H1) this response answers.
  • status ("running" | "ok" | "errors"): Current execution state.
  • time (string): ISO 8601 timestamp.
  • errors (array): List of error objects containing:
    • cell: Cell address (e.g. "A5", "H9", or "(notebook)").
    • type: Exception class (e.g. "ValueError", "LayoutError", "FormatVersionError", "StartupError").
    • message: Truncated error message (capped at 1,000 characters).
    • traceback: Truncated traceback (capped at 4,000 characters).
  • note (string): Summary message.

What a Request Does

Every request reloads the notebook from the code sheet, waits until the new session reports that nothing is running or queued (up to the AI edit settle timeout in System Limits), and answers with the same request number. Cells Marimo cannot parse appear in errors as SyntaxError entries, because Marimo never runs them. A ModuleNotFoundError for a cell includes a pointer to the package list. If the notebook is still running when the wait ends, status is "errors" and note says it did not finish in time.

Response Truncation Caps

To ensure the response JSON fits inside Excel’s single-cell limit (32,767 characters): - Total response payload is capped at 32,000 characters. - If tracebacks exceed the limit, tracebacks are shortened to 300 characters each. - If the payload still exceeds 32,000 characters, trailing errors are omitted, and note indicates: Tracebacks were shortened and X of Y errors were left out to fit the cell limit.

Editing via Office.js vs. OpenPyXL

Office.js (Within Excel)

  • Read and write _BOARDFLARE!A3:A<last> using standard range.values.
  • Ensure range formats are text (@) so code is not parsed as formulas.
  • Read _BOARDFLARE!H1, write back its integer plus 1, then poll _BOARDFLARE!H2 until its request equals the new H1 value and status is not "running".

OpenPyXL (External File Modification)

  • Load the workbook with openpyxl.load_workbook(filename).
  • Access the _BOARDFLARE worksheet.
  • Modify column A and/or cell H9.
  • Save the workbook. The next time the workbook is opened in Excel with Boardflare installed, the add-in automatically detects the changed source, updates its caches, and runs the notebook.
  • Note: OpenPyXL may discard non-standard Excel parts such as certain embedded controls or drawing artifacts.

Uploading and Importing Python Scripts

The add-in task pane allows uploading local .py Marimo notebook files: - File extension: Must be a .py file. - Encoding: Must be valid UTF-8 encoded text. - Content: Must be non-empty (cannot contain only whitespace). - Size limit: Total file size must not exceed the 200,000-byte workbook storage limit.