Skip to content
mctpPublic

About

Integration of the pi coding agent with marimo notebook, replicating the basic functionality of Claude Science.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

petri

Petri

A project template for data analysis in marimo notebooks, paired with the pi coding agent.

The agent does not edit notebook files. It attaches to the running marimo kernel and changes cells there. The notebook in your browser and the notebook the agent works on are the same object.

How it works

You work from two places at once — a browser tab for the notebook, a terminal for make and the agent — and both reach the same running kernel:

                            ┌─────────────┐
                            │     YOU     │
                            └──┬───────┬──┘
                ┌──────────────┘       └──────────────┐
                │                                     │
    ┌───────────▼──────────┐              ┌───────────▼──────────┐
    │      BROWSER         │              │      TERMINAL        │
    │  the notebook in a   │              │  make + your coding  │
    │  browser tab         │              │  agent               │
    │                      │              │                      │
    │  · write cells       │              │  · make nb    start  │
    │  · read plots and    │              │  · make check verify │
    │    tables            │              │  · "add a cell that  │
    │  · watch them re-run │              │    plots the fit"    │
    └───────────┬──────────┘              └───────────┬──────────┘
                │                                     │
                │ websocket                  execute-code.sh · cm
                └──────────────────┬──────────────────┘
                                   ▼
  ╔═════════════════════════════════════════════════════════════════╗
  ║          ONE LIVE KERNEL  —  one per open notebook              ║
  ║                                                                 ║
  ║    your cells run here · Python + one embedded R                ║
  ║    you and the agent share it, and it — not the                 ║
  ║    file on disk — is the source of truth                        ║
  ╚═════════════╤═════════════════════════════════════╤═════════════╝
                │ cells import                        │ writes
    ┌───────────▼──────────┐              ┌───────────▼──────────┐
    │  petri  ·  scripts/  │              │  notebooks/yours.py  │
    │  the API + your own  │              │  autosaved — only by │
    │  pure functions      │              │  the browser's tab   │
    └──────────────────────┘              └──────────────────────┘

Two consequences worth knowing before you start. The .py file is output — the kernel writes it, so editing it by hand while marimo runs loses the edit. And since the agent shares your kernel, a cell it adds appears in your tab without a reload.

Requirements

  • uv — Python and virtualenv
  • git, bash, curl, jq
  • R, renv (optional, for rpy2)

Quickstart

git clone <this-template> my-project && cd my-project
make setup          # install dependencies and git hooks
make init full      # copy the examples in (or `make init` for just the notebooks)
make nb             # start marimo on notebooks/ (port derived from this directory)

Start pi in the project root in a second terminal and ask the agent to pair on the notebook.

Multiple tabs open? The agent attaches to one kernel. If more than one tab has the same notebook open, it will ask which session (kernel) to edit. To find it, open the hamburger menu (three lines next to settings) → Pair with an agent and copy the instructions — no commands needed, just look it up.

Your folders ship empty, make init to jump-start from petri/examples/:

Installs
make init blank.py, py_example.py, r_example.py — standalone, no data needed
make init full the above plus full_example.py, scripts/, and the data it reads

full_example.py runs the whole pipeline in one notebook: it reads data/external/, calls scripts/, publishes to data/shared/, then consumes that table and writes deliverables to data/preserved/. Nothing is overwritten on a re-run unless you pass ARGS=--force. make help lists all targets.

Conventions

  • Notebooks are .py files. They diff, review, and run as scripts (e.g. make run NB=notebooks/foo.py or PYTHONPATH=. uv run python notebooks/foo.py).
  • Do not edit a notebook file while its kernel runs. The kernel overwrites it.
  • Secrets go in .env. Document new keys in .env.example.
  • marimo's project settings are in [tool.marimo] in pyproject.toml. marimo reads that section and never writes to it, so a key typed into the AI panel is written to your own ~/.config/marimo/marimo.toml, not to a tracked file. Personal preferences — theme, font size, keymap — belong there too.
  • The marimo-pair skill is a fork in petri/skills/, owned by this repo. See petri/docs/marimo-pair-fork.md.
  • Agent instructions are in AGENTS.md.

Agents

The repo works with pi and with Claude Code. The skills are in petri/skills/ with the rest of it, and each agent's entry point is a symlink — one copy, two readers, and neither tool's directory owns the content:

petri/skills/             the skills themselves: marimo-pair, petri-analysis, petri-init
.pi/skills       -> ../petri/skills
.claude/skills   -> ../petri/skills
CLAUDE.md        -> AGENTS.md
.claude/settings.json     permission rules: the marimo scripts and make run without a
                          prompt; nb-stop, run, export, check and test still ask

Only one agent attaches to the kernel: the one you are talking to. Subagents it spawns stay outside — their own process, no marimo, no cells — and hand back code and numbers as text. The main agent turns that into a cell, so their work still reaches your notebook through the one kernel you share with it, and you still watch it arrive.

Layout

Yours, empty until make init:

notebooks/     marimo notebooks
scripts/       the heavy lifting: pure functions, no I/O
data/          your data: external/ shared/ preserved/ cache/ (see below)

All three are gitignored, so what make init installs never gets committed back as a second, drifting copy of an example. That also means your own work in them is untracked by default. Once a notebook or a transformation is yours rather than installed output, put it under version control deliberately — git add -f notebooks/my_analysis.py, or drop the notebooks/* and scripts/* lines from .gitignore if the repo is now your project rather than a copy of the template. scripts/ especially: a manifest records the SHA-256 of the module a cell imported, so make check errors once that file changes or goes missing, and git is the only thing that can bring it back.

The two language toolchains are supported:

                manifest        lockfile     library
Python          pyproject.toml  uv.lock      .venv/
R               (none)          renv.lock    .renv/

For renv, see: petri/docs/renv.md.

Petri infrastructure (do not delete):

petri/         paths, provenance, R interop — the API your notebooks import
  examples/    what `make init` copies out: notebooks/ scripts/ data/
  tests/       provenance contracts and the write-path test
  docs/        architecture, sessions, git-lfs, renv, rpy2, marimo-pair-fork, todo
  assets/      petri-logo.png
  skills/      marimo-pair, petri-analysis, petri-init — symlinked into .pi/ and .claude/
  init.py      the `make init` sets
  server.py    the per-directory marimo port, behind `make nb`
  outline.py   a notebook's cells without reading the file, behind `make nb-outline`
.python-version (uv)
.Rprofile (R sources it from the startup directory)
.pre-commit-config.yaml
AGENTS.md (agent instructions)

Data flow

Data moves one way. A cell loads an external file, passes it through a pure function from scripts/, and publishes the result — and the two layers petri owns get a manifest with every write:

  data/external/ ──→ scripts/ ──→ data/shared/ ──→ data/preserved/
   given to you      pure fns      save_shared()    preserve_*()
   never written                   + manifest       + manifest
                                       ▲                 ▲
                                       └────────┬────────┘
                                                │
                            make check ─────────┘
                            re-hashes each artifact,
                            says what drifted

Each data layer is named for the function that writes it.

Directory Written by Read by
data/external/ nobody — inputs from outside the cell that publishes
data/shared/ save_shared() any notebook
data/preserved/ preserve_figure(), preserve_table(), preserve_file() people

data/shared/ is the only channel between notebooks. data/preserved/ holds deliverables: a figure bundle is a PDF, a PNG, the plotted source data, and a manifest. Every write records provenance, and make check verifies it.

Git ignores all of data/, manifests included. Provenance is verified where the data is, by make check, rather than shipped through the repository — a manifest is a generated file, and you decide what of your own work to version. Committing one is deliberate (git add -f), worth doing when a collaborator has to verify a table they cannot download. To push the inputs themselves rather than sending them around, see petri/docs/git-lfs.md.

data/cache/ is beside the three but is not a layer — nothing verifies it. It is scratch space with a stable path, ignored by git, and safe to delete. The one thing petri writes there is data/cache/preview/, the low-resolution figure previews an agent reads instead of a print-resolution deliverable. data/notebook_htmls/ is the same kind of directory: make export writes rendered notebooks there, and every one of them can be rebuilt.

See petri/docs/architecture.md for the design and petri/provenance.py for the API.

Commands

make init full  # copy the examples into notebooks/, scripts/, data/
make nb         # start marimo, or print the URL if it is already running here
make nb-stop    # stop this project's server, leaving other projects alone
make check      # verify artifacts against their manifests
make test       # contracts and the write path
make lint       # ruff check and format

Each project gets its own marimo port, derived from its directory, so several petri checkouts run side by side and make nb-stop never touches another one. make nb-url prints this project's URL rather than assuming marimo's default.

Dependencies

Declared in pyproject.toml, locked in uv.lock. Add with uv add <pkg>. During a pairing session let the agent use ctx.packages.add(...) so the kernel stays in sync.

R packages use renv:

make r-restore                             # rebuild .renv/library from renv.lock
make r-install PKG="ggplot2 bioc::DESeq2"  # install and snapshot

rpy2 needs a local R installation. See petri/docs/renv.md and petri/docs/rpy2.md.

About

Integration of the pi coding agent with marimo notebook, replicating the basic functionality of Claude Science.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages