---
id: daily-marimo-pair
name: "marimo-pair"
url: https://skills.yangsir.net/skill/daily-marimo-pair
author: marimo-team
domain: data-ai
tags: ["notebook-automation", "marimo", "reactive-notebook", "pair-programming", "data-analysis"]
install_count: 2900
rating: 4.40 (18 reviews)
github: https://github.com/marimo-team/marimo-pair
---

# marimo-pair

> 以结对编程方式与 marimo notebook 交互，可读取和编辑单元格代码、安装包、运行单元格、检查响应式图，用户实时在浏览器中查看结果

**Stats**: 2,900 installs · 4.4/5 (18 reviews)

## Before / After 对比

### 数据分析迭代速度

**Before**:

在 Jupyter Notebook 中手动添加单元格、复制粘贴代码、重新运行整个 notebook 以验证依赖关系，每次迭代需要 5 分钟

**After**:

通过结对编程协议自动创建和编辑单元格，实时检查响应式图依赖，每次迭代在 30 秒内完成并立即显示结果

| Metric | Before | After | Change |
|---|---|---|---|
| 迭代时间 | 300秒 | 30秒 | -90% |

## Readme

# marimo-pair

# marimo Pair Programming Protocol

This skill gives you full access to a running marimo notebook. You can read
cell code, create and edit cells, install packages, run cells, and inspect
the reactive graph — all programmatically. The user sees results live in their
browser while you work through bundled scripts or MCP.

## Philosophy

marimo notebooks are a dataflow graph — cells are the fundamental unit of
computation, connected by the variables they define and reference. When a cell
runs, marimo automatically re-executes downstream cells. You have full access
to the running notebook.

- **Cells are your main lever.** Use them to break up work and choose how and
when to bring the human into the loop. Not every cell needs rich output —
sometimes the object itself is enough, sometimes a summary is better.
Match the presentation to the intent.

- **Understand intent first.** When clear, act. When ambiguous, clarify.

- **Follow existing signal.** Check imports, `pyproject.toml`, existing cells,
and `dir(ctx)` before reaching for external tools.

- **Stay focused.** Build first, polish later — cell names, layout, and styling
can wait.

## Prerequisites

### How to invoke marimo

Only servers started with `--no-token` register in the local server registry
and are auto-discoverable — starting without a token makes discovery easier.
If a server has a token, set the `MARIMO_TOKEN` environment variable before
calling the execute script (avoids leaking the token in process listings). The
right way to invoke marimo depends on context (project
tooling, global install, sandbox mode). See
[finding-marimo.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/finding-marimo.md) for the full decision tree.

**Do NOT use `--headless` unless the user asks for it.** Omitting it lets
marimo auto-open the browser, which is the expected pairing experience. If the
user explicitly requests headless, offer to open it with
`open http://localhost:<port>`.

## Troubleshooting

### User keeps getting prompted to allow Bash commands

The skill declares `allowed-tools` in its frontmatter, but Claude Code may
still prompt for each Bash call. To fix this, the user should add the absolute
paths to the scripts to their `.claude/settings.json` (project-level) or
`~/.claude/settings.json` (global):

```
{
  "permissions": {
    "allow": [
      "Bash(bash /absolute/path/to/skills/marimo-pair/scripts/discover-servers.sh *)",
      "Bash(bash /absolute/path/to/skills/marimo-pair/scripts/execute-code.sh *)"
    ]
  }
}

```

## How to Discover Servers and Execute Code

Two operations: **discover servers** and **execute code**.

Operation
Script
MCP

Discover servers
`bash scripts/discover-servers.sh`
`list_sessions()` tool

Execute code
`bash scripts/execute-code.sh -c "code"`
`execute_code(code=..., session_id=...)` tool

Execute code (multiline)
`bash scripts/execute-code.sh <<'EOF'`
same

Execute code (direct URL)
`bash scripts/execute-code.sh --url URL -c "code"`
same (with `url` param)

Scripts auto-discover sessions from the registry on disk. Use `--port` to
target a specific server when multiple are running, `--session` to target a
specific session when multiple notebooks are open on the same server, or
`--url` to skip discovery entirely and hit a server URL directly (e.g.
`--url http://localhost:2718`). `--url` is the only way to connect to
remote servers since auto-discovery only reads the local registry. Only
use `--url` with trusted servers — data is sent to the endpoint, so a
malicious URL could exfiltrate notebook contents. Set the `MARIMO_TOKEN`
env var to authenticate when the server has token auth enabled (`--token`
flag also works but exposes the token in process listings). If the
server was started with `--mcp`, you'll have MCP tools available as an
alternative.

### Discovery finds nothing but the user has a server running?

Only `--no-token` servers are in the registry. If discovery comes up empty,
the server likely has token auth — ask the user for the token and set it as
the `MARIMO_TOKEN` environment variable.

### No servers running?

**Always discover before starting.** Background task "completed" notifications
do not mean the server died — check the output or run discover first.

If no servers are found, read the user's intent — if they want a notebook,
start one. **Always start marimo as a background task** (using
`run_in_background` on the Bash tool) so the server automatically gets cleaned
up when the session ends and doesn't block the conversation. See
[finding-marimo.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/finding-marimo.md).

If there's no `.py` file yet, pick a descriptive filename based on context
(e.g., `exploration.py`, `analysis.py`, `dashboard.py`). Don't ask — just
pick something reasonable.

**Avoid shell escaping issues.** `-c` works for simple one-liners, but for
multiline code or code with quotes/backticks/`${}`, use a heredoc or a file:

```
# heredoc (single-quoted delimiter prevents shell interpolation)
bash scripts/execute-code.sh <<'EOF'
import marimo._code_mode as cm

async with cm.get_context() as ctx:
    ctx.create_cell("x = 1")
EOF

# file
bash scripts/execute-code.sh /tmp/code.py

# direct URL (skips auto-discovery and works with remote servers)
bash scripts/execute-code.sh --url http://localhost:2718 -c "1 + 1"

```

## Executing Code

Every execute-code call runs inside the notebook's kernel. All cell variables
are in scope — `print(df.head())` just works. Nothing you define persists
between calls (variables, imports, side-effects all reset), but you can freely
introspect the notebook: inspect variables, test code snippets, check types
and shapes. Use this to explore, prototype, and validate before committing
anything to the notebook — then create cells to persist state and make results
visible to the user.

To mutate the notebook's dataflow graph — create, edit, and delete cells,
install packages, and run cells — use `marimo._code_mode`:

```
import marimo._code_mode as cm

async with cm.get_context() as ctx:
    cid = ctx.create_cell("x = 1")
    ctx.install_packages("pandas")
    ctx.run_cell(cid)

```

You **must** use `async with` — without it, operations silently do nothing.
All `ctx.*` methods are **synchronous** — they queue operations and the
context manager flushes them on exit. Do **not** `await` them.

**Cells are not auto-executed.** `create_cell` and `edit_cell` are structural
changes only — use `run_cell` to queue execution.

`code_mode` is a tested, safe API for notebook mutations — prefer it for all
structural changes. You also have access to marimo internals from the kernel,
but treat that as a last resort and only with high confidence after exploration.

**UI state lives outside the reactive graph.** Anywidget traitlets can be read
or set directly (e.g., `slider.value = 5`). For `mo.ui.*` elements, use
`ctx.set_ui_value(element, new_value)` inside `code_mode`.

### First Step: Explore the API

The `code_mode` API can change between marimo versions — and each running
server could be a different version. Inspect what's available at the start of
each session, especially when switching between servers.

```
import marimo._code_mode as cm

async with cm.get_context() as ctx:
    ctx  # inspect me — dir(), help(), .cells, ...

```

## Guard Rails

Skip these and the UI breaks:

- **Install packages via `ctx.install_packages()`, not `uv add` or `pip`.**
The code API handles kernel restarts and dependency resolution correctly.
Only fall back to external CLIs if the API is unavailable or fails.

- **Custom widget = anywidget.** For bespoke visual components, use anywidget
with HTML/CSS/JS. Composed `mo.ui` is fine for simple forms and controls.
See [rich-representations.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/rich-representations.md).

- **NEVER write to the `.py` file directly while a session is running — the kernel owns it.**

- **No temp-file deps in cells.** `pathlib.Path("/tmp/...")` in cell code is a bug.

- **Avoid empty cells.** Prefer `edit_cell` into existing empty cells rather
than creating new ones. Clean up any cells that end up empty after edits.

- **Don't worry about cell names.** Most cells don't need explicit names —
see [notebook-improvements.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/notebook-improvements.md#cell-names).

## Widgets and Reactivity

Anywidget state (traitlets) lives outside marimo's reactive graph. To hook a
widget trait into the graph, pick one strategy per widget — never mix them:

- **`mo.state` + `.observe()`** — you pick specific traits to bridge. Default choice.

- **`mo.ui.anywidget()`** — wraps all synced traits into one reactive `.value`. Convenient but coarser.

Read [rich-representations.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/rich-representations.md) before wiring either.

## Keep in Mind

- **The user is editing too.** The notebook can change between your calls —
re-inspect notebook state if it's been a while since you last looked.

- **Deletions are destructive.** Deleting a cell removes its variables from
kernel memory — restoring means recreating the cell and re-running it and
its dependents. If intent seems ambiguous, ask first.

- **Installing packages changes the project.** `ctx.install_packages()` adds
real dependencies — confirm when it's not obvious from context.

## References

- [finding-marimo.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/finding-marimo.md) — how to find and invoke the right marimo

- [gotchas.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/gotchas.md) — cached module proxies and other traps

- [rich-representations.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/rich-representations.md) — custom widgets and visualizations

- [notebook-improvements.md](https://github.com/marimo-team/marimo-pair/blob/HEAD/reference/notebook-improvements.md) — improving existing notebooks

Weekly Installs662Repository[marimo-team/marimo-pair](https://github.com/marimo-team/marimo-pair)GitHub Stars184First SeenMar 4, 2026Security Audits[Gen Agent Trust HubPass](/marimo-team/marimo-pair/marimo-pair/security/agent-trust-hub)[SocketPass](/marimo-team/marimo-pair/marimo-pair/security/socket)[SnykWarn](/marimo-team/marimo-pair/marimo-pair/security/snyk)Installed oncodex577opencode564kimi-cli559gemini-cli559amp559github-copilot559

---
*Source: https://skills.yangsir.net/skill/daily-marimo-pair*
*Markdown mirror: https://skills.yangsir.net/api/skill/daily-marimo-pair/markdown*