Skip to content

Marimo reactivity through the Jupyter-shaped API (1.9.39) - #38

Merged
echarles merged 6 commits into
mainfrom
feat/marimo-jupyter-protocol
Sep 26, 2026
Merged

echarles merged 6 commits into
mainfrom
feat/marimo-jupyter-protocol

Conversation

@echarles

Copy link
Copy Markdown
Member

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's execute_code reads — 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_code names 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, and reactions — every cell re-run because of it, in order, each a typed Reaction (cell, code, its own ExecutionResult). A failing reaction stops the chain and is reported. run_code_streaming yields the reactions' events after the cell's, each tagged marimo_cell_id / marimo_reaction.
  • CodeSandboxClient: execute, execute_code, execute_code_streaming take cell_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 under marimo, each with its own Jupyter-shaped outputs, status, execution_count; execute_interactive emits them after the cell's own, every message tagged metadata.marimo = {cell_id, reaction}, the way IOPub messages carry their parent. reactive, run_cell, register_cell, remove_cell, plan, graph, cells pass through to a reactive sandbox and raise TypeError elsewhere.
  • models.py: Reaction, and cell_id / reactions on ExecutionResult; the marimo_reactions extra attribute is gone.
  • Docs (providers/marimo.mdx), six new tests on the in-process kernel, changelog, 1.9.39.

…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
Copilot AI lite review requested due to automatic review settings September 26, 2026 08:14
@echarles echarles mentioned this pull request Sep 26, 2026
1 task done

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 High severity · 1 Medium severity · 1 Low severity

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.

Comment thread code_sandboxes/client.py Outdated
{
"cell_id": reaction.cell_id,
"code": reaction.code,
"status": "error" if reaction.result.code_error is not None else "ok",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread code_sandboxes/marimo_sandbox.py Outdated
result.reactions.append(
Reaction(cell_id=dependent, code=dependent_code, result=dependent_result)
)
if dependent_result.code_error is not None:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread code_sandboxes/models.py
Comment on lines +400 to +413
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"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@echarles
echarles merged commit 227b0d0 into main Sep 26, 2026
8 checks passed
@echarles
echarles deleted the feat/marimo-jupyter-protocol branch September 26, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📦 Support Marimo Sandboxes

2 participants