Work back and forth
This is the tutorial where you actually use the thing. Claude reads the sheet, changes it, formats it, sorts it, filters it and explains it — and the cells you marked stay yours. Not "the model was asked nicely not to look": the values are replaced before the reply leaves your machine, and every call is written to a log you own.
The deal, in one line: **you mark, the bridge masks, and the refusals are recorded.** By the end you will have seen sixteen tools run and four refusals land, and you will know the one place the guard cannot reach — which matters more than any of the rest, because a reader who does not know it will get burned.
Prerequisites
- Tutorial 1 done: the add-in is installed and the AI Bridge tab is on the
ribbon.
- Tutorial 2 done: a Claude client is connected to the bridge.
- The demo workbook, built from nothing so it looks the same on every machine:
python docs\tutorials\make_demo_workbook.py
That writes TutorialDemo.xlsx beside the script: a Budget sheet with a
header row, ten team rows (3-12), a Total row (13), a deliberate #DIV/0!
in G12, and two cells this tutorial makes private — B14, a director's
salary, and B15, a payroll account number. Every value in it is invented.
- Open
TutorialDemo.xlsxand bind it (Tutorial 2), so the AI Bridge tab reads
AI Bridge - connected. Nothing here touches any other workbook.
Everything below happened in one recorded run against that workbook. Nothing is mocked up.
Part 1 — what you mark, and where the mark lives
- Select `B14:B15` and press `Mark private`. The toggle is the whole
interface: one press always leaves the selection wholly private, a second
press leaves it wholly public. There is no half-marked state to reason
about, which is deliberate — a partial mark is the kind of thing you
misremember later. Watch the Status group on the right of the tab: it now
reads 2 private cells on 1 sheet, and it is reading the workbook, not
remembering what you clicked.
The AI Bridge tab with Mark private pressed and the status group showing two private cells
- Press `Select all`. It selects every private range on the sheet. This is
the answer to "wait, what did I mark three weeks ago?" — you do not have to remember, and you do not have to trust a highlight being switched on.
Select all has selected the two marked cells
- Press `Highlight private`. It shades the marked cells so they stand out
while you work. It is a toggle, it is remembered per workbook, and it is off by default — because a shared file should not announce which cells are sensitive to everyone who opens it. It is conditional formatting only: press it again and your own fill, font and borders are exactly as they were.
Highlight private shading the two marked cells
- The marks live in the file, not in the bridge. Each marked range is
stored as a workbook Name beginning AI_PRIVATE_, scoped to its sheet and
derived from the sheet and the range's own address. Two consequences worth
knowing. First, the marks travel inside the .xlsx: mail the file, open it
on another machine, run no bridge at all, and the cells are still marked.
Second, the names are created hidden — which is why the Name Manager below
(Ctrl+F3) shows no AI_PRIVATE_ entries even though two ranges are marked.
They stay out of the Name Box and the Paste Name list so they cannot clutter
a formula you are typing.
Excel's Name Manager, showing none of the hidden AI_PRIVATE names
- What the guard follows, stated plainly. Before marking, this workbook
got a second sheet, Summary, whose B1 holds =Budget!B14. Marking B14
lists that cell — the add-in scans the other sheets for direct references
into the range you are marking. Summary!B1 is masked: the guard reads
formula text on every sheet and masks any cell that references a private
one, chains included, so putting the formula one tab over does not get
around it. The dialog offers to mark those cells outright as well; say
Yes and they stay covered even if someone later pastes the formula as
a value. What the guard does not follow, because it reads text and not
Excel's calculation engine: INDIRECT, OFFSET arithmetic, a reference
that reaches the range through a defined name, and a value you already
copied and pasted somewhere else as a constant. Mark those yourself.
The cross-sheet dialog naming Summary!B1 and listing what the scan cannot find
Part 2 — letting Claude read
- Ask for a range that contains a marked cell. Read
A1:E13andA14:B15
in one prompt. The first range comes back whole. The second comes back with
[redacted] where the values were, a masked list naming B14 and B15,
and a one-line masking_note: *"2 cells masked by AI_PRIVATE names: B14,
B15. Values shown as [redacted]; do not infer them."* Note what the reply
does and does not say. It names addresses, never contents — the mask tells
Claude where the hole is so it can work around it, and nothing more. The
substitution happens on the way out of the bridge process, before the reply
is serialised, which is the only place it is worth doing: instructing a model
to disregard a value it has already been sent is not privacy, it is etiquette.
Claude reporting the redacted cells and the masking note
- **Ask what is protected right now:
list_private_cellsand
privacy_report.** The first lists every masked address on every sheet. The
second adds the audit — and note that privacy_report answers with the same
guard.private_cells call that does the masking, so the list and the mask
cannot drift apart. Addresses and counts only, again.
Claude listing the private cells and the privacy report
- The audit trail is not a feature you switch on. Every tool call — every
read, every write, every refusal — is appended to
%LOCALAPPDATA%\ExcelAIBridge\audit\<key>.jsonl, where <key> is a hash of
the workbook path, with the timestamp, the tool, the identity of the caller
(the Google account that signed in, token:<id> for a bearer token, or
local), the sheet and range, how many cells were masked, and whether the
call was refused and why. It is on this machine and it is append-only. The
refusals in Part 3 all show up here, which is how you can answer "did it ever
try?" with a file instead of an opinion.
The recent audit entries in the privacy report
- `analyze_sheet` — the shape of one sheet. A structural map of the active
sheet: where the header is, which blocks are data, which are formulas, which are inputs. Cheaper than reading every cell and it is the thing to ask for before a big question, so Claude reasons about the model rather than guessing from a corner of it. Private cells are masked here too, so the map is shaped correctly around a hole it cannot see into.
Claude's structural map of the Budget sheet
- **
analyze_workbook— every sheet at once, and the guard following the
formula across tabs.** It maps each sheet, counts the private cells per
sheet, and draws the edges: which sheet feeds which, read out of the
formula text. Here it reports two sheets, `Budget` with two private cells
and `Summary` with one, and an edge from `Summary` to `Budget`. `Summary!B1`
comes back as `[redacted]` even though nobody marked it and we answered
**No** in step 5: the guard read `=Budget!B14` and treated the cell as
private because of what it reads. That is the boundary of a cell-level
mask stated honestly — it follows what is written in the formula, and
nothing that is not.

- `trace` — what feeds a cell, and what it feeds. Trace
E12and you get
its precedents from the formula text and its dependents from Excel. Trace
`B14`, the private one, and the trace stops there: the node is returned by
address, marked private, with `[redacted]` where its value and formula
would be. You can see the shape of the model right up to the boundary and
not one cell past it. One asymmetry to know: precedents are read from
formula text and so cross sheets, while dependents come from Excel and do
not — the reply says so itself.

- `find_errors` — every error cell with what feeds it. The demo workbook
ships one on purpose: `G12` is `=D12/C12` on the Contractors row, whose
headcount is `0`, so it shows `#DIV/0!`. `find_errors` finds it and carries
its direct precedents along, so the cause — the zero in `C12` — is usually
in the same reply as the symptom.

- `get_instructions` — your standing orders for this workbook. Claude
reads them from a plain markdown file beside the journal:
`%LOCALAPPDATA%\ExcelAIBridge\instructions\<key>.md`, the same
workbook key as the audit. Ask for `get_instructions` and the reply tells
you the exact path. **You edit that file by hand — no tool writes it**, and
that is a deliberate design choice, not an omission: text a model can write
and then later obey is exactly the loop this bridge is built to avoid. Put
house rules there ("never touch column G", "figures are in thousands") and
every conversation starts with them.

Part 3 — letting Claude change things, and taking it back
- `write_cells` — a normal write. Ask for a note in
G4, an empty cell.
It lands, and the reply is an acknowledgement — the addresses written and a
journal index — never an echo of the data. Before the cell is touched, the
prior contents are appended to a write journal on this machine. That order
matters: journal first, then write, so the worst case is a record for a
write that never happened, rather than a write no record ever saw.

- **The same write over a non-empty cell — refused, and it says what is
there.** Ask to put the note in `A13` and the batch is refused with
`A13 already holds 'Total'; pass overwrite=true to replace it`. Nothing is
written and nothing is journalled. The refusal exists because the expensive
mistake is not a wrong value, it is a *silently destroyed* one: an assistant
filling in what it believes is an empty template, over a populated sheet, is
a plausible accident with an unbounded blast radius. Making it say the
quiet part out loud — *there is already something here, it says `Total`,
do you still want this?* — costs one round trip and turns a data loss into a
decision. The whole batch is refused, not the one cell, so you never get a
half-applied write.

- Say yes and it goes in. Repeat with overwriting allowed and the write
lands. You made the call; the journal has the old value; step 27 puts it
back.

- `format_cells` — number format, bold, fill. One call sets a thousands
separator on `D3:D12`, makes it bold, and fills it. Formatting is journalled
exactly like a write, with the prior number format, bold state and fill
colour of every cell, so it is reversible for the same reason.

- And in the sheet. This is the point of a bridge to a live workbook
rather than an upload: the change is in front of you, in Excel, while you
are still in the conversation.

- `undo_last_write` — the safety net. It restores the cells changed by
the most recent un-reverted change and reports what it put back. It is not
Excel's `Ctrl+Z`: a change made through the bridge does not enter Excel's own
undo stack at all, so this journal is the only way back. It refuses rather
than guesses in four cases — a different workbook, a missing sheet, a cell
that has been marked private since, and a cell that no longer holds what the
bridge wrote (someone edited it in the meantime; restoring would destroy
their edit and report success). A refusal leaves that record at the front of
the queue and says so, because it then blocks the undo of everything
beneath it.

- `sort_range` over a range containing a private cell — refused. Ask to
sort `A2:E15` by monthly cost and the guard refuses, naming `B14` and `B15`.
This is the headline of the whole tutorial, so it is worth being precise
about *why*, because "it's a write, and writes to private cells are blocked"
is only half of it. A sort does not read a value out to anybody — but the
order it leaves behind *is* the comparison. Sort a range that contains a
hidden number and the row it lands on tells you how that number compares to
every other number in the range; run it a few times on different keys and
you have bracketed the value you were never allowed to see. So the guard
treats a sort as touching **every cell in the range**, not just the ones it
would rewrite, and refuses the whole operation. Nothing is journalled,
because nothing happened.

- And the sheet did not move. Same rows, same order. The refusal is not a
message shown after the fact — the guard runs before Excel is asked to do
anything at all.

- `sort_range` on a clean range works, and undoes. Sort just the data
rows, `A2:E12`, by monthly cost descending: it sorts. A sort is journalled
as the whole prior grid rather than cell by cell — the operation moved every
cell, so there is no per-cell "is this still ours?" test to make — and
`undo_last_write` puts the entire block back exactly as it was, the same way
Excel's own undo of a sort behaves.

- `apply_filter` — a view, not an edit. Filter the data rows to one team.
Rows hide; no cell changes; nothing is journalled. Be precise here, because
it is the one native operation that does **not** consult the guard: a filter
is not refused over a range containing a private cell, because it neither
reads a cell nor writes one, and the reply carries no row contents. (The
repository README's blanket "each refuses a range that contains a private
cell" overstates this one; the code is the authority and it does not check.)

- `clear_filter` removes it — undo does not. Because the filter was never
journalled, `undo_last_write` knows nothing about it. The pair to reach for
is `apply_filter` / `clear_filter`, and the reply from the first says so in
a note.

- **
add_conditional_formatandadd_validation_list— both refuse a range
containing a private cell.** A conditional format over `A2:E15` is refused
naming `B14` and `B15`; a validation dropdown on `B14` is refused the same
way. Both refusals are for the same reason as the sort: a rule that colours
a cell when it exceeds a threshold, or a dropdown that rejects what does not
match a list, is a channel that reports on a value nobody was allowed to
read. Private means untouchable, not merely unreadable.

- On a clean range, both apply — and both are journalled. A conditional
format that reddens `D3:D12` above a threshold, and a Yes/No dropdown on
`G3`. Each is journalled with what it wrote, so `undo_last_write` deletes
exactly the rule this bridge added and leaves rules you made yourself alone.

- Undo everything outstanding, and the sheet is back. Ask Claude to keep
calling `undo_last_write` until it answers that there is nothing left to
undo. The validation goes, the conditional format goes, the overwrite of
`A13` goes, the note in `G4` goes. What remains is what you did with your
own hands: the two private marks and the highlight.

What the AI can never do
- Read the value of a cell you marked. It is replaced with
[redacted]
inside the bridge process, before the reply is built. Same for the formula text, and same for any cell on that sheet that is computed from it.
- Reorder a range that contains one.
sort_rangerefuses the whole range,
because the resulting order is itself a comparison of the hidden value.
- Colour one conditionally, or constrain it with a dropdown. Same refusal,
same reason.
- Overwrite something silently. A non-empty target refuses the whole batch
and names what is in the way; a private target refuses one step earlier and is never quoted at all.
- Escape the record. Every call, including every refusal, is appended to the
audit journal on your machine with who asked, what they asked for, and how many cells were hidden.
And the two things it can do that you should keep in mind, because they are limits of the mechanism rather than bugs in it:
- A copy is not a mask. The guard covers the cells you named plus their
dependents on the same sheet. A formula on another tab, a value pasted
elsewhere, a reference through INDIRECT, OFFSET or a defined name — none
of those are followed. Step 10 shows exactly this happening. Keep private
values and everything derived from them on one sheet, or mark the copies too.
- A filter is not blocked. It changes nothing and reports nothing, so it is
not refused. Do not read the sort refusal as "every native operation is guarded" — read it as "every operation that could carry a value out, is".
Reproducing this run
Every screenshot above came from one unattended run of
scenarios/capture-work-back-and-forth.ahk, which drives both sides — Excel's
ribbon and the Claude composer — and writes a journal of frames and window
rectangles that tutorial_tool.py binds to the committed images by hash. The
coverage claim in this tutorial is not checked by reading its prose: the gate
reads the bridge's own audit journal and requires an entry for every tool inside
the run's time window. A tool named here but never actually called fails the
build.