Marimo reactivity through the Jupyter-shaped API (1.9.39) - #38
Conversation
…e context, reactions typed on the result, in the reply, in the hook messages and the stream, the graph through the client; 1.9.39
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Critical reaction status and chain-stopping issues remain, along with metadata and output-preservation gaps.
Review effort: Lite
Findings: 2
Open (4)
What changed in this PR
Extends Marimo reactivity through Jupyter-shaped client APIs, replies, streams, and typed execution results.
Changes:
- Adds typed cell identities and reaction results.
- Propagates reactions through replies, interactive hooks, and streaming.
- Adds tests, documentation, changelog updates, and version
1.9.39.
| File | Summary and findings |
|---|---|
tests/test_marimo_sandbox.py |
Adds reactivity API coverage. |
docs/docs/providers/marimo.mdx |
Documents the Jupyter-shaped reactivity behavior. |
code_sandboxes/models.py |
Adds Reaction and execution metadata. Nit (2 votes): Re-export Reaction from the package root. |
code_sandboxes/marimo_sandbox.py |
Propagates reactions and streaming tags. Critical (3 votes): Stop reaction chains using the complete execution success predicate. Moderate (4 votes): Preserve cell_id on syntax-error early returns. |
code_sandboxes/client.py |
Exposes reactions through client replies and streams. Critical (4 votes): Derive reaction status from reaction.result.success. Moderate (1 vote): Preserve output termination when building nested outputs. Moderate (1 vote): Carry cell_id and reactions through CodeExecutionOutcome. |
code_sandboxes/__version__.py |
Bumps the package version. |
CHANGELOG.md |
Records the release changes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| { | ||
| "cell_id": reaction.cell_id, | ||
| "code": reaction.code, | ||
| "status": "error" if reaction.result.code_error is not None else "ok", |
There was a problem hiding this comment.
Fixed in c39f6d2: the reaction's status is "ok" if reaction.result.success else "error", the same predicate as the reply's own status, so an interrupted, infrastructure-failed or non-zero-exit reaction is reported as failed.
| result.reactions.append( | ||
| Reaction(cell_id=dependent, code=dependent_code, result=dependent_result) | ||
| ) | ||
| if dependent_result.code_error is not None: |
There was a problem hiding this comment.
Fixed in c39f6d2: the chain stops on not dependent_result.success — a raise, an interrupt, a sandbox error or a non-zero exit all stop it, as the docstring says. Covered by a test where a dependent exits with SystemExit(3).
| registration=registration, | ||
| ) | ||
| result = self._plain_run(code, timeout=timeout, **handlers) | ||
| result.cell_id = cell_id |
There was a problem hiding this comment.
Fixed in c39f6d2: the syntax-error result carries cell_id too, so a named run_code that was refused still reports its cell and its streamed error event is tagged. Covered by a test.
| class Reaction(BaseModel): | ||
| """One cell re-run because the cell that just ran changed what it reads. | ||
|
|
||
| A reactive sandbox (the Marimo variant) runs, after every execution, the | ||
| cells that depend on it, in dependency order. Each of those runs is a | ||
| reaction: the cell, its code, and the result it produced, so a caller who | ||
| only speaks the Jupyter protocol still learns what else changed. | ||
| """ | ||
|
|
||
| model_config = ConfigDict(extra="allow") | ||
|
|
||
| cell_id: str | ||
| code: str | ||
| result: "ExecutionResult" |
There was a problem hiding this comment.
Fixed in c39f6d2: Reaction is imported and listed in __all__ beside ExecutionResult; from code_sandboxes import Reaction works.
…s predicate, a refused cell is still named, Reaction is exported; MarimoCells drives the same reactivity over any Jupyter-shaped client
…fields, what re-runs and when it stops, what the graph refuses and what it only reports, the graph calls, the Jupyter-shaped API and its stream tags, the MarimoCells driver, and how the helper works
…s and the contributing page follow



Closes #37.
The Marimo sandbox (#35) reacted only through
run_cell; a consumer speaking the Jupyter protocol —CodeSandboxClient.execute,execute_interactive, the streams the MCP server'sexecute_codereads — got the cell's own outputs and never learned what else re-ran. This carries the reactivity through that API without changing the wire.run_codenames its cell through the execution context (Context(id="a"); the same id replaces the cell rather than adding a second) and the result says what happened:cell_id, andreactions— every cell re-run because of it, in order, each a typedReaction(cell, code, its ownExecutionResult). A failing reaction stops the chain and is reported.run_code_streamingyields the reactions' events after the cell's, each taggedmarimo_cell_id/marimo_reaction.CodeSandboxClient:execute,execute_code,execute_code_streamingtakecell_id(the context is passed only when one is named, so every other variant and subclass is called as before); a reply carries the reactions undermarimo, each with its own Jupyter-shapedoutputs,status,execution_count;execute_interactiveemits them after the cell's own, every message taggedmetadata.marimo = {cell_id, reaction}, the way IOPub messages carry their parent.reactive,run_cell,register_cell,remove_cell,plan,graph,cellspass through to a reactive sandbox and raiseTypeErrorelsewhere.models.py:Reaction, andcell_id/reactionsonExecutionResult; themarimo_reactionsextra attribute is gone.providers/marimo.mdx), six new tests on the in-process kernel, changelog, 1.9.39.