Skip to content

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 --force. make help lists all targets.

Conventions

  • Notebooks are .py files. They diff, review, and run as scripts.
  • 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 live in [tool.marimo] in pyproject.toml. marimo reads that section and never writes to it, so a key typed into the AI panel lands in your own ~/.config/marimo/marimo.toml, not in 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 live 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 allowlist for the marimo scripts and make

Layout

Yours, empty until make init:

notebooks/     marimo notebooks
scripts/       your transformations: 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, renv, rpy2
  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`
.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 Git
data/external/ nobody — inputs from outside the cell that publishes ignored
data/shared/ save_shared() any notebook manifests only
data/preserved/ preserve_figure(), preserve_table(), preserve_file() people tracked

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.

data/cache/ sits beside the three but is not a layer — petri never writes it and nothing verifies it. It is scratch space with a stable path, ignored by git, and safe to delete.

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