diff --git a/.agents/skills/marimo-pair/SKILL.md b/.agents/skills/marimo-pair/SKILL.md
deleted file mode 100644
index 15d5b088..00000000
--- a/.agents/skills/marimo-pair/SKILL.md
+++ /dev/null
@@ -1,293 +0,0 @@
----
-name: marimo-pair
-description: >-
- Drive a live marimo notebook as a workspace: run Python in the same kernel
- the user does, inspect live notebook state, and commit durable notebook
- changes. Use when the user wants to start a marimo notebook or pair on an
- active marimo session.
-allowed-tools: Bash(bash **/scripts/discover-servers.sh *), Bash(bash **/scripts/execute-code.sh *), Read
----
-
-marimo is a reactive Python runtime for building reproducible Python programs
-(marimo notebooks). Cells are connected by the variables they define and
-reference. Running a cell re-executes dependents in dataflow order. The active
-runtime holds the kernel namespace, cell state, and dataflow graph. The
-notebook (`.py` file) is the artifact the kernel writes from that state while a
-session is running.
-
-A user interacts with the same runtime via a notebook UI with cells, outputs,
-and widgets.
-
-**WARNING. The active runtime is the source of truth.** During a session, you
-SHOULD NOT modify the associated `.py` file directly. File edits WILL NOT reach
-the active kernel or user, and the kernel may overwrite them on save. Use
-`marimo._code_mode` (`cm`) for notebook changes. Reading disk is fine, but
-prefer `ctx.cells[...].code` for current cell code.
-
-The harness reports the absolute path to this `SKILL.md`. Resolve bundled
-`scripts/...` and `reference/...` paths from its parent directory, even when
-the current working directory is a notebook workspace. In command examples,
-replace `/absolute/path/to/marimo-pair` with that directory.
-
-## Required first kernel command
-
-Start every code-mode session with this dedicated command:
-
-```bash
-bash /absolute/path/to/marimo-pair/scripts/execute-code.sh \
- --url http://localhost:2718 \
- -c "import marimo._code_mode as cm; help(cm)"
-```
-
-Follow this order for each kernel, including read-only tasks:
-
-1. Run the inspection command once.
-2. Wait for successful `help(cm)` output.
-3. Then use `cm.get_context()` or another `cm` API in a later call.
-
-Do not run task-specific `cm` code before the inspection command succeeds.
-
-## Connect to a Notebook
-
-Use the bundled `execute-code.sh` from the reported skill directory or MCP
-(`execute_code(...)`) to run Python in a live marimo kernel.
-
-`execute-code.sh` always takes `--url`. If the user provides a notebook URL,
-run the required inspection against it directly:
-
-```bash
-bash /absolute/path/to/marimo-pair/scripts/execute-code.sh \
- --url http://localhost:2718 \
- -c "import marimo._code_mode as cm; help(cm)"
-```
-
-After that command succeeds, pass task code with `-c CODE`, `-` for stdin, or
-a file path:
-
-```bash
-bash /absolute/path/to/marimo-pair/scripts/execute-code.sh \
- --url http://localhost:2718 - <<'PY'
-import marimo._code_mode as cm
-
-async with cm.get_context() as ctx:
- cid = ctx.create_cell("x = df.head()")
- ctx.run_cell(cid)
-PY
-```
-
-If the user gives no URL, find or start a notebook. Look for a running server
-with `bash /absolute/path/to/marimo-pair/scripts/discover-servers.sh`, MCP
-`list_sessions()`, or local process context, and pass the `url` it reports to
-`--url`. With one notebook open, the script targets it automatically; with
-several, pass `--file` with the notebook's file key.
-
-If no server is running and the user wants a notebook, start marimo with
-`--no-token` (and without `--headless`) so it auto-registers for discovery. The
-notebook UI must be open for `execute-code` to target it. The right invocation
-depends on context (project tooling, global install, sandbox mode). If the
-notebook file contains a PEP 723 `#
-/// script` header, it MUST be opened with `--sandbox` — otherwise marimo
-ignores the inline dependencies. See
-[finding-marimo.md](reference/finding-marimo.md) for the full decision tree and
-[execution-context.md](reference/execution-context.md) for selector resolution,
-scripts, MCP, and shell quoting.
-
-## Scratchpad Scope
-
-`execute-code` evaluates Python in marimo's scratchpad: a temporary namespace
-with a shallow copy of the kernel globals. Notebook variables are available by
-name, but new top-level bindings and rebindings are discarded after each call.
-In-place mutations to notebook-owned objects can persist because those names
-still reference live objects.
-
-Each call reports stdout and stderr from the scratchpad, plus console output
-from notebook cells it causes to run, including reactive descendants.
-
-### Ordinary Python
-
-Use ordinary Python in the scratchpad to inspect variables, sample data, test
-transformations, probe APIs, check imports, and read widget state.
-
-```python
-print(df.head())
-
-x = 10
-print(x)
-```
-
-Here `df` comes from notebook globals, while `x` is a scratchpad-local binding.
-`x` exists for this call only and WILL NOT be added to notebook globals.
-
-### Persist with `cm`
-
-Top-level scratchpad assignments and rebindings are temporary. To persist work,
-including new variables, you MUST submit changes through `marimo._code_mode`
-(`cm`).
-
-`marimo._code_mode` is a PRIVATE, UNSTABLE agent API (note the leading
-underscore). It exists for tools like this skill to drive a live kernel from
-the scratchpad. DO NOT import it from notebook cells, library code, or
-anything a user would run — methods can change or disappear across marimo
-versions and kernels. Treat every `import marimo._code_mode as cm` as
-scratchpad-only.
-
-Open a code-mode context to queue notebook changes.
-
-```python
-import marimo._code_mode as cm
-
-async with cm.get_context() as ctx:
- cid = ctx.create_cell("x = df.head()")
- ctx.run_cell(cid)
-```
-
-The scratchpad supports top-level async code. Use `async with` directly;
-wrapping it in `asyncio.run(...)` is unnecessary and can conflict with the
-kernel's event loop.
-
-After this block exits and the new cell runs, `x` is notebook state. Later
-scratchpad calls can read `x` by name. Code later in the same scratchpad call
-should read `ctx.globals["x"]`, because the scratchpad namespace was copied
-before the cell ran.
-
-Inside the context, queued mutation methods are synchronous. Call them
-directly; do not `await` them. Each call queues an operation for marimo to
-apply when the context exits normally. If the block raises, the queue is
-discarded.
-
-On clean exit, marimo applies packages, validates and applies structural cell
-changes, runs queued cells, then may run dependents. Validation is only
-structural since queued cell runs can still error. `create_cell` and
-`edit_cell` change notebook structure only. Use `run_cell` to execute.
-
-`create_cell` currently defaults to `hide_code=True`, which collapses the code
-editor in the UI. Pass `hide_code=False` if the user wants created cells to
-be visible without manually expanding them.
-
-
-## Marimo Rules
-
-marimo imposes a small contract on notebook code so it can keep the notebook as
-a directed acyclic graph (DAG):
-
-- **No cycles** - cells cannot depend on each other in a cycle.
-- **No public redefinitions across cells** - each name has one owning cell.
-- **No wildcard imports** - `import *` prevents static analysis of definitions.
-
-These rules keep the kernel, UI, and saved artifact consistent.
-
-When `cm` submits a cell body, marimo parses its top-level definitions and
-references. Public names enter the graph. Names that start with `_` are local
-to their cell and unavailable to other cells. If a `cm` edit violates the
-contract, marimo rejects the structural change and returns the validation
-error.
-
-## The Notebook's Shape
-
-A notebook is an ordered collection of cells. `ctx.cells` is the document view
-and `ctx.graph` is the dataflow view.
-
-```python
-for cell in ctx.cells:
- cell # .id, .code, .name, .config, .status, .errors
-
-ctx.cells["setup"] # by name
-ctx.cells[0] # by position
-list(ctx.cells.keys()) # all IDs, in notebook order
-```
-
-Cell IDs are opaque strings which can be queried from the notebook or captured
-from `cm` return values:
-
-```python
-cid = ctx.create_cell("df = pd.read_csv('data.csv')")
-print(cid) # e.g. 'Hbol'
-```
-
-Alternatively, cells can be assigned and referenced by `name`. The graph can be
-used to understand its role in the dataflow.
-
-```python
-for cid, impl in ctx.graph.cells.items():
- impl # .defs, .refs (sets of public names)
-
-ctx.graph.descendants(cid) # cells that re-run when this one changes
-ctx.graph.ancestors(cid) # cells this one depends on
-```
-
-In marimo, deletes are *destructive* so it can be useful to query the
-descendants prior to deleting to understand it's impact.
-
-## Writing Notebook Changes
-
-The graph contract keeps marimo able to run and save the notebook. Passing
-those checks alone does not guarantee a useful artifact. Committed cells should
-still be readable, rerunnable, and editable.
-
-Make durable edits that reuse the notebook's existing names, imports,
-dependencies, and UI model. Don't be lazy. Avoid one-off workarounds that pass
-`cm` validation but leave a brittle notebook.
-
-### Cell Bodies
-
-Submit the code that belongs in the cell.
-
-- **Submit cell contents** - `create_cell` and `edit_cell` take cell contents,
- not saved-file `@app.cell` wrappers.
-- **Read before replacing** - for now, another editor may change a cell between
- scratchpad calls. Before `edit_cell`, read the current body from
- `ctx.cells[...]` and submit the full replacement.
-- **Reuse notebook imports** - if `np` already exists, use it or edit the owning
- import cell. DO NOT add `import numpy as _np` just to bypass the graph.
-- **Define each public name once** - a public name has one owning cell.
- Reassigning it in another cell fails with `Multiply-defined names`; edit the
- owning cell or give the result a new name. See
- [gotchas.md](reference/gotchas.md).
-- **Run cells deliberately** - `create_cell` and `edit_cell` change structure
- only. Queue `ctx.run_cell(...)` when the cell should execute.
-
-### Cell Boundaries
-
-A cell is also a rerun boundary. Put expensive or reusable computation
-upstream of presentation so UI edits stay cheap. Keep cheap,
-presentation-specific work with the view when that is easier to read.
-
-Use `mo.vstack` and `mo.hstack` only when the composition is part of the UI.
-Narrative often reads better in an adjacent markdown cell.
-
-### Prefer `cm`-Managed Changes
-
-Use `cm` APIs when they exist. Avoid direct file edits, shell package commands,
-and scratchpad-only state for changes that should persist.
-
-- **Do not edit the `.py` artifact** - DO NOT use `Edit`, `Write`, or
- `NotebookEdit` on the notebook file during a live session. Use
- `ctx.edit_cell(...)` even for small changes.
-- **Manage packages through `cm`** - use `ctx.packages.add()` or
- `ctx.packages.remove()` instead of direct `uv` or `pip`; confirm
- non-obvious dependency changes.
-- **Avoid transient paths** - persisted cells should not depend on `/tmp/...`
- unless the work is intentionally transient.
-- **Delete deliberately** - deleting a cell removes globals it defines. Reuse
- empty cells when convenient and delete cells left empty after edits.
-
-### UI and Widgets
-
-Inspect the object before changing it. Different UI objects update through
-different paths.
-
-- **Set `mo.ui.*` through `cm`** - use `ctx.set_ui_value(element, value)` inside
- `cm.get_context()`.
-- **Set anywidget traitlets directly** - synced traitlets are Python
- attributes, for example `widget.value = 5`.
-
-For designing custom visual or interactive output, see
-[rich-representations.md](reference/rich-representations.md).
-
-## References
-
-- [execution-context.md](reference/execution-context.md) — scripts, MCP, auth, startup, and shell quoting
-- [finding-marimo.md](reference/finding-marimo.md) — choosing the right marimo invocation
-- [gotchas.md](reference/gotchas.md) — name redefinition, cached module proxies, and notebook traps
-- [rich-representations.md](reference/rich-representations.md) — custom widgets and visualizations
-- [notebook-improvements.md](reference/notebook-improvements.md) — improving existing notebooks
diff --git a/.agents/skills/marimo-pair/reference/execution-context.md b/.agents/skills/marimo-pair/reference/execution-context.md
deleted file mode 100644
index 6db6f322..00000000
--- a/.agents/skills/marimo-pair/reference/execution-context.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# Connection Troubleshooting
-
-Use this reference when `execute-code.sh` or MCP cannot reach the intended
-marimo session, cannot select a session, or fails because code was passed
-incorrectly.
-
-## Targeting
-
-- Pass the discovered `--url` unchanged; `null` means no address was reachable.
-- With one open notebook, the script resolves its session.
-- Use `--file` for an exact, stable file key or path. It resolves the current
- session on every call.
-- Use `--session` for an exact active session ID; it goes stale on reconnect.
-- The selectors are mutually exclusive. Preserve supplied values; never guess.
-
-## Auth
-
-For token-authenticated servers, prefer `MARIMO_TOKEN`.
-
-```bash
-MARIMO_TOKEN=... bash scripts/execute-code.sh --url http://localhost:2718 -c "1 + 1"
-```
-
-`--token` also works, but may expose the token in process listings. If both are
-present, `--token` overrides `MARIMO_TOKEN`. The script sends the token as
-`Authorization: ******` on session discovery and code execution requests.
-
-## Code Input
-
-Pass code with `-c CODE`, `-` for stdin, or a file path.
-
-```bash
-bash scripts/execute-code.sh --url http://localhost:2718 - <<'PY'
-print(df.head())
-PY
-```
-
-```bash
-bash scripts/execute-code.sh --url http://localhost:2718 /tmp/code.py
-```
-
-## Common Script Errors
-
-- **`[]` from discover-servers.sh** - nothing is registered. Only servers
- started with `--no-token` register; otherwise ask the user for the URL, or
- start marimo with the project's normal tooling.
-- **No active sessions on the server** - open the notebook in the browser.
-- **No active session matches `--file`** - check the exact file key against the
- available paths printed by the script.
-- **Multiple sessions on server** - several notebooks are open; rerun with the
- `--file` path or `--session` ID shown for the notebook you want.
-- **Failed to connect** - check the URL, token, and whether the server is still
- running.
-- **Execution did not complete** - the server ended the stream without a
- result. With `--file`, retry the same command so it resolves the current
- session. With `--session`, the ID is probably stale after a page refresh.
-- **... is running on the Windows host but answered at no address reachable
- from WSL** - WSL's network cannot reach it. The message lists the fixes; the
- usual one is restarting marimo on the host with `--host 0.0.0.0`. Running the
- scripts from Git Bash or PowerShell on the Windows side also works.
-- **needs jq / curl on PATH** - install them in whichever environment runs the
- scripts. Inside WSL that means the distro, not Windows.
-- **SyntaxError** - the submitted Python was malformed; use a heredoc or file.
-- **ImportError** - diagnose in the notebook kernel. Install packages through
- `cm` when needed.
-
-## Starting marimo
-
-Discover first. If no server is running and the user wants a notebook, use
-[finding-marimo.md](finding-marimo.md).
diff --git a/.agents/skills/marimo-pair/reference/finding-marimo.md b/.agents/skills/marimo-pair/reference/finding-marimo.md
deleted file mode 100644
index 793afc68..00000000
--- a/.agents/skills/marimo-pair/reference/finding-marimo.md
+++ /dev/null
@@ -1,117 +0,0 @@
-# Finding and Invoking 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).
-
-```sh
-marimo edit notebook.py --no-token [--sandbox]
-```
-
-Start marimo in edit mode without `--headless` unless the user asks for a
-headless server. The notebook UI must be open in a browser before marimo has an
-active session for `execute-code` to target. If running headless, give the user
-the local URL and wait for them to open it before executing code.
-
-How you invoke `marimo` depends on context — find the right way to run it.
-
-## Where the registry lives
-
-A registering server writes one JSON file per instance and removes it on clean
-shutdown: `$XDG_STATE_HOME/marimo/servers` (default `~/.local/state/marimo/servers`)
-on Linux and macOS, `%USERPROFILE%\.marimo\servers` on Windows.
-
-Inside WSL, both are in play. A notebook started in the distro registers at the
-XDG path; one started on the Windows host registers in the Windows profile,
-which `discover-servers.sh` also reads. Windows-host servers show up with
-`"origin": "windows-host"`, and reaching them depends on WSL networking: under
-the default NAT the distro has its own loopback, so a host notebook bound to
-`127.0.0.1` is unreachable and only `--host 0.0.0.0` (or mirrored networking)
-lets WSL connect. The `url` in each entry already accounts for this, so use it.
-
-Cross-OS discovery needs WSL interop: `cmd.exe` to locate the Windows profile,
-and `tasklist.exe` to tell a live Windows PID from a dead one. With interop
-disabled, only servers inside the distro are found.
-
-## Notebooks with PEP 723 metadata require `--sandbox`
-
-Before picking a runner, check the notebook file for a PEP 723 header:
-
-```python
-# /// script
-# requires-python = ">=3.11"
-# dependencies = [
-# "marimo",
-# "polars",
-# ]
-# ///
-```
-
-If the block is present, the notebook was authored as a self-contained
-sandboxed script and **SHOULD be opened with `--sandbox`**. Without the flag
-marimo runs in the ambient environment and silently ignores the inline
-dependencies. Imports will fail or, worse, resolve to a different version than
-the author pinned.
-
-`--sandbox` works regardless of project context: inside a uv project, `uv run
-marimo edit notebook.py --no-token --sandbox` still creates the isolated env
-from the PEP 723 block rather than the project's `.venv`.
-
-## Inside a Python project
-
-If there's a `pyproject.toml` in cwd or a parent directory, check that marimo
-is actually in the dependencies before using the project's runner. Look for
-`marimo` in:
-
-- `[project.dependencies]`
-- `[project.optional-dependencies]` or `[dependency-groups]` (dev deps)
-- `[tool.pixi.dependencies]`
-- The project's `.venv` (`uv pip show marimo` or check `.venv/bin/marimo`)
-
-If marimo is in a named dependency group (not the default), you need to
-specify it:
-
-```sh
-# marimo is in [dependency-groups] → "notebooks" group
-uv run --group notebooks marimo edit notebook.py --no-token
-```
-
-Once you know marimo is available, use whatever CLI runner the project uses:
-
-```sh
-# uv-managed project
-uv run marimo edit notebook.py --no-token
-# pixi-managed project
-pixi run marimo edit notebook.py --no-token
-```
-
-Skip `--sandbox` here — the project already manages dependencies.
-
-If `pyproject.toml` exists but marimo is **not** in the deps, treat this as
-"outside a project" (see below).
-
-## Outside a Python project
-
-Prefer `--sandbox`. Sandbox mode creates an isolated environment for the
-notebook and writes dependencies into the script itself as inline PEP 723
-metadata — so the notebook stays self-contained and reproducible.
-
-```sh
-# With uv available (preferred)
-uvx marimo@latest edit notebook.py --no-token --sandbox
-
-# With marimo installed globally
-marimo edit notebook.py --no-token --sandbox
-```
-
-## Global marimo install
-
-If marimo is installed globally, check the version — code mode shipped in
-v0.21.1. If the installed version is older, prompt the user to upgrade before
-proceeding.
-
-## Nothing found
-
-If no project marimo, no `uv`/`uvx`, and no global `marimo` on PATH, tell the
-user to install `uv` (