Skip to content

[Feature]: Standard .ipynb interoperability — export session notebooks to nbformat (and later import / open in JupyterLab) #293

Description

@imjszhang

Preflight checklist

  • I searched existing issues and discussions and this hasn't been proposed yet.

What problem does this solve?

The notebook runtime persists each session as an append-only run.json
(NotebookRunDocument / NotebookRunRecord[]) — great for durability and crash
recovery, but the results are locked inside the app:

  • A finished analysis can't be handed to a collaborator who uses JupyterLab / VS Code,
    pushed to a paper repository, or rendered on GitHub / nbviewer.
  • The Session Notebook viewer already shows a disabled "Download as .ipynb" button
    ("Notebook export is coming soon", added in feat: add read-only Session notebook viewer #130), so the need is acknowledged in the
    UI but not tracked as an issue yet.
  • Competing open workbenches in this category treat real .ipynb files + "Open in
    JupyterLab" as a headline feature; for researchers whose lab workflow is already
    Jupyter-based, the lack of interop is a real migration cost away from Open Science.

The good news: NotebookRunRecord is semantically almost isomorphic to an nbformat
code cell (scriptsource, outputsoutputs, executionCount
execution_count), so this is a projection problem, not a storage redesign.

Proposed solution

A three-stage plan. Each stage is independently shippable; stage 1 alone activates the
existing disabled button.

Stage 1 — one-way export (run.json.ipynb, nbformat 4.5)

A pure projection function in the main process. No change to existing storage;
run.json stays the single source of truth.

Field mapping:

NotebookRunRecord nbformat cell Notes
script source (split into lines) direct
kernelKind: python / r code cell direct
kernelKind: repl / bash code cell with a shell marker (e.g. %%bash) + metadata tag control-plane runs downgraded to shell cells
executionCount execution_count direct
text.stdout / stderr stream output direct
outputs (images/tables) display_data with proper mimetypes (image/png, text/html, …) artifact files read back and inlined as base64
text.traceback error output direct
runId, startedAt, environment metadata.open_science.* custom namespace; Jupyter tools preserve unknown metadata

Notebook-level metadata: kernelspec (python3 / ir) plus
metadata.open_science (session id, environment name, app version) — so the exported
file renders out of the box in JupyterLab / VS Code / GitHub preview while keeping
enough info to trace cells back to their runs.

Mixed-kernel sessions: .ipynb allows only one kernelspec. Proposal: set the
kernelspec from the dominant kernel and tag foreign-kernel cells via
metadata.open_science.kernel, plus an optional "split export by kernel" mode
(session-python.ipynb + session-r.ipynb).

Stage 2 — import + "Open in JupyterLab"

  • Import: drop an .ipynb → parse cells → append one NotebookRunRecord per cell
    (an imported/not-yet-executed status), so users re-run cells inside Open Science.
    Reuses the existing append-only structure; no new storage format.
  • Open in JupyterLab: the app already manages conda environments via micromamba;
    installing jupyterlab into the bound environment and launching it against the
    exported notebook + the session's data directory is a thin wrapper.

Stage 3 (optional) — mirror-on-run

After each run completes, incrementally rewrite an .ipynb mirror next to run.json.
The mirror is strictly a projection — external edits to the mirror are not written
back (detected external modification prompts "import as new runs" instead). This avoids
the whole bidirectional-sync problem class.

Engineering discipline

  1. The projection is a pure function with round-trip tests:
    export(import(nb)) === nb for the supported subset, and export(run.json) is
    idempotent. nbformat has a JSON Schema to assert against.
  2. No Python dependency for conversion — nbformat is just JSON; generate/parse it
    in TypeScript in the main process. No jupyter nbconvert subprocess.
  3. metadata.open_science.runId links every cell back to its run record — the same
    anchor a future provenance chain (Phase 2 on the roadmap) can hang off, so this
    feature and provenance reinforce each other rather than competing.

Alternatives considered

  • Adopt .ipynb as the native storage format (like some competing workbenches):
    rejected — it would be a rewrite of a working runtime (run journaling, crash
    recovery, multi-kernel routing) and .ipynb is a poor fit for an append-only
    execution log. Projection keeps the engine and gains the interop.
  • Export via jupyter nbconvert in the managed Python env: works but adds a
    slow subprocess + Python dependency for what is a pure JSON transformation.
  • Do nothing: the disabled button stays "coming soon", and Jupyter-native labs
    keep paying a migration cost to adopt Open Science.

Additional context

  • The disabled export button shipped in feat: add read-only Session notebook viewer #130 (read-only Session notebook viewer).
  • Related roadmap items: Phase 2 "Artifact versioning and a provenance chain" — the
    per-cell runId metadata proposed here is a natural provenance anchor.
  • Happy to contribute this: stage 1 (export + round-trip tests) is a well-scoped PR
    against src/main/notebook/ + activating the button in
    SessionNotebookDialog.tsx. If maintainers agree with the mapping above, I can
    start with a draft PR.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions