Record staleness reads on .code access only#9655
Merged
Merged
Conversation
`_CellsView._cell_view` previously recorded a read on every wrapper build, so access patterns like: ```py first = ctx.cells[0] ctx.edit_cell(first.id, ...) ``` passed the read-before-write guard without the agent ever reading the source. Reads now fire from accessing `NotebookCell.code` (and `_CellsView.__repr__`) goes through the wrapper so its previews still count.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
mscolnick
approved these changes
May 21, 2026
Contributor
There was a problem hiding this comment.
No issues found across 2 files
Architecture diagram
sequenceDiagram
participant Agent as Agent Code
participant CellsView as _CellsView
participant NotebookCell as NotebookCell
participant Ctx as RuntimeContext
participant Cell as _NotebookCell
Note over Agent,Cell: Staleness Guard — Reads Recorded on .code Access Only
Agent->>CellsView: Access cells (index, iterate, find, grep)
CellsView->>NotebookCell: Construct wrapper (no read recorded)
NotebookCell-->>Agent: NotebookCell wrapper
Note over Agent,NotebookCell: Agent holds wrapper — no staleness recorded yet
alt Agent accesses .code property
Agent->>NotebookCell: cell.code
NotebookCell->>NotebookCell: Check _record_read callback
NotebookCell->>Ctx: _note_read(cell.id, cell.version)
Ctx->>Ctx: Record staleness timestamp
NotebookCell->>Cell: Delegate to _cell.code
Cell-->>NotebookCell: Source code string
NotebookCell-->>Agent: Source code
else Agent calls __repr__ on wrapper
Agent->>NotebookCell: repr(cell) or repr(cells)
NotebookCell->>NotebookCell: __repr__ reads self.code
NotebookCell->>Ctx: _note_read(cell.id, cell.version)
Ctx->>Ctx: Record staleness timestamp
NotebookCell->>Cell: Delegate to _cell.code
Cell-->>NotebookCell: Code snippet
NotebookCell-->>Agent: Formatted repr string
else Agent edit_cell without prior .code read
Agent->>CellsView: edit_cell(cell_id, new_code)
CellsView->>Ctx: Check staleness for cell_id
alt No prior read recorded
Ctx-->>CellsView: StaleCellError
CellsView-->>Agent: Raise StaleCellError
else Read was recorded
Ctx-->>CellsView: Allowed
CellsView->>Cell: Update cell code
Cell-->>CellsView: Success
CellsView-->>Agent: Ack
end
end
Contributor
There was a problem hiding this comment.
Pull request overview
This PR tightens the code-mode staleness guard so that a “read” is recorded only when cell source is actually accessed (via NotebookCell.code), rather than when a wrapper is merely constructed. This prevents edit-before-read patterns from incorrectly bypassing the staleness protection.
Changes:
- Move staleness read recording from
_CellsView._cell_view()toNotebookCell.codevia an injectedrecord_readcallback. - Ensure
_CellsView.__repr__counts code previews as reads by going through the wrapper’s.code. - Expand staleness tests to cover indexing/iteration/find/grep/repr behavior under the new “reads only on
.codeaccess” invariant.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
marimo/_code_mode/_context.py |
Defers read tracking until .code is accessed; updates CellsView.__repr__ to record reads via wrapper previews. |
tests/_code_mode/test_staleness.py |
Updates and extends tests to assert that wrappers/searches alone don’t satisfy staleness, while .code and repr previews do. |
Comment on lines
+387
to
+389
| # `repr(ctx.cells)` shows first-line code previews of every cell, | ||
| # so the agent has seen source snippets — editing without a | ||
| # separate read must succeed. |
| nb.edit_cell("0", "a = 99") | ||
| nb.edit_cell("1", "b = 99") | ||
| with pytest.raises(StaleCellError): | ||
| nb.edit_cell("0", "a = 99") |
Comment on lines
+313
to
+316
| Indexing, iteration, find, and grep all just hand out wrappers — they | ||
| do not by themselves prove the agent has seen the code. Without this | ||
| invariant, `first = ctx.cells[0]; ctx.edit_cell(first.id, ...)` would | ||
| blindly clobber code the agent never looked at. |
Contributor
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.23.7-dev85 |
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
_CellsView._cell_viewpreviously recorded a read on every wrapper build, so access patterns like:passed the read-before-write guard without the agent ever reading the source. Reads now fire from accessing
NotebookCell.code(and_CellsView.__repr__) goes through the wrapper so its previews still count.