Contributor Guide#
This page is intended for people interested in building new or modified functionality for Jupyter AI.
If you would like to build applications that enhance Jupyter AI, please see the developer’s guide.
Development Setup#
Overview#
In v3, Jupyter AI is no longer a monorepo. The various components of Jupyter AI
now live as separate repos under the
jupyter-ai-contrib GitHub org.
jupyter-ai is now a metapackage that distributes a stable working set of these
components with some default configuration, and is mostly empty on its own.
The following repositories are stable, i.e., this means the repos are, or are quickly becoming, stable and ready for production use.
jupyter-ai-chat-commands - Default set of chat commands in Jupyter AI
jupyter-ai-jupyternaut - Package which provides the Jupyternaut, the default AI persona, in Jupyter AI
jupyter-ai-litellm - LiteLLM based model abstraction for Jupyter AI
jupyter-ai-persona-manager - The core manager & registry for AI personas in Jupyter AI
jupyter-ai-router - The core routing layer used in Jupyter AI to process chat messages
jupyter-ai-tools - Jupyter Server extension that exposes a collection of powerful, agent-friendly tools for interacting with notebooks and Git repositories
jupyter-server-documents - Server side document handling with improved output handling and kernel management/status.
jupyter-server-mcp - An MCP interface/extension for Jupyter Server
jupyterlab-diff - JupyterLab commands to show cell and file diffs
jupyter-ai-magic-commands - The code for handling the
%aiand%%aimagic commands in Jupyter notebooks.jupyter-ai-acp-client - A client implementation of the Agent Client Protocol (ACP) as well as helper classes for other developers to use when custom AI personas wrapping ACP agents.
jupyterlab-commands-toolkit - JupyterLab commands as an AI Toolkit
These repositories are experimental and under active development:
jupyter-ai-personas - AI Personas for Jupyter AI
jupyter-ai-demos - A set of demos for new features of Jupyter AI
jupyter-floating-chat - A jupyterlab extension to add a floating chat input
jupyter-server-ai-tools - A Jupyter Server extension for discovering tools across extensions.
jupyterlab-cell-input-footer - JupyterLab Plugin that provides a cell input footer
jupyterlab-document-collaborators - A JupyterLab extension for showing collaborators at the top of a document
jupyterlab-magic-wand - An in-cell AI assistant for JupyterLab notebooks
jupyterlab-notebook-awareness - Track current notebook and active cell in JupyterLab’s awareness
jupyterlab-ai-commands - A set of JupyterLab commands for use with AI agents
jupyterlab-ai-commands - A set of commands for AI in JupyterLab.
New experimental submodules are added frequently (the list above is not exhaustive), and the current set may be accessed at: https://github.com/orgs/jupyter-ai-contrib/repositories.
Using the devrepo#
jupyter-ai-devrepo is a “developer repo” intended for Jupyter AI contributors. This facilitates installing Jupyter AI along with all its submodules with a small number of steps. By cloning the repo and following the steps below, you can have an editable developer installation of all Jupyter AI subpackages.
0. Clone the repo#
git clone --recurse-submodules https://github.com/jupyter-ai-contrib/jupyter-ai-devrepo.git
cd jupyter-ai-devrepo/
1. Install root dependencies#
This monorepo requires git, uv, and just. Use homebrew to install these if you do not have them installed.
No dedicated Python environment is required because uv automatically manages a local venv.
If you use conda/mamba/micromamba, you can run the following commands to
install these dependencies into the base environment:
{conda,mamba,micromamba} activate base
{conda,mamba,micromamba} install uv just
# make sure to activate the `base` environment before working in this repo
Otherwise, you can use your OS’s package manager. For example, on macOS:
brew install uv just
2. Pull in latest changes#
This command switches to the main branch on every submodule and pulls from it.
just pull-all
If you do not have your RSA key set up to access GitHub from the CLI, you will be prompted to authenticate yourself on GitHub for each of the subpackages as they get cloned into the jupyter-ai-devrepo folder.
While this is not usually necessary, you can also run here:
just sync --refresh
Depending on your environment, sometimes it will clear errors and
Refresh UV’s cache to check for updated package versions
Synchronize your local Python environment with the updated lock file(s)
Ensure all dependencies from the newly pulled submodules are installed
Resolve any dependency conflicts.
3. Install all packages#
This command automatically installs each of the packages in editable mode.
just install-all
4. Start JupyterLab#
Start JupyterLab by running:
just start
This command will always run uv run jupyter lab from the root of this devrepo,
even if your current directory is inside of a submodule.
This is analogous to running
uv run jupyter lab --config={{justfile_directory()}}/jupyter_server_config.py
If you want the root directory to be different from the justfile directory, you can also run an extended command:
uv run jupyter lab --config=jupyter_server_config.py --notebook-dir=<start_directory>
Devrepo commands#
Global commands (can be run from anywhere in the devrepo):
just start: start JupyterLabCtrl + Z+kill -9 %1stops JupyterLab in caseCtrl + Cdoes not work
just sync: synchronize the Python environment withuv.lockjust pull-all: switch tomainin all submodules and pull in all upstream changesjust build-all: build all frontend assets in every submodulejust enable-extensions: enable all server and lab extensionsjust install-all: perform an editable, developer installation of all packages (includesbuild-allandenable-extensions)just uninstall-all: uninstall everything (removes.venvanduv.lock)just reinstall-all: re-install everything (useful for fixing a broken venv)just clean: remove generated files (*.chat,*.qasm,*.ipynb)
Submodule-only commands (must be run from within a submodule directory):
just jlpm: install JavaScript dependencies for the current submodulejust build: build frontend assets for the current submodulejust lint: run linting for the current submodulejust pytest: run pytest for the current submodulejust reinstall: reinstall the current submodule package
Building documentation#
The just install script should automatically install the documentation
dependencies. You will need to install pandoc as well. You can install pandoc from the conda-forge channel:
conda install -c conda-forge pandoc
Otherwise have a look at pandoc’s installation instructions.
To build the documentation locally, run
cd jupyter-ai/docs/
make html
and open file://<JUPYTER-AI-ABSOLUTE-PATH>/docs/build/html/index.html, where
<JUPYTER-AI-ABSOLUTE-PATH> is the absolute path of the Jupyter AI monorepo on
your local filesystem. It is helpful to bookmark this path in your browser of
choice to easily view your local documentation build.
After making any changes, make sure to rebuild the documentation locally via
make html, and then refresh your browser to verify the changes visually.
Subpackage documentation#
Jupyter AI is composed of many subpackages under the
jupyter-ai-contrib org. Each
subpackage can ship its own contributor and developer docs inside its own repo,
and this site pulls them in as versioned subpages so everything is readable in
one place. User-facing docs stay solely in jupyter-ai.
Note
This is rolling out gradually — not every subpackage has contributor or developer docs yet. Subpages appear here automatically as subpackages add them. A subpackage with no docs simply contributes no pages.
What a subpackage should define#
Inside a subpackage repo, add either or both of:
docs/source/contributors/index.md # → appears under Contributors on this site
docs/source/developers/index.md # → appears under Developers on this site
Notes:
The
index.mdH1 heading becomes the subpage’s title in the navigation. By convention, use the repo name (e.g.# jupyter-ai-tools).Each section is independent — a subpackage may define one, both, or neither.
You may ship a full subtree: nested pages and images under
docs/source/{contributors,developers}/are copied verbatim, and yourindex.md’s own{toctree}structures them.Only these two directories are aggregated. Everything else in the subpackage’s
docs/is ignored by the main site.
Auto-generating an API reference#
Subpackage docs can pull a Python API reference straight from source instead
of hand-writing it, using Sphinx autodoc directives inside an {eval-rst} block:
```{eval-rst}
.. autoclass:: my_package.MyClass
:members:
.. autopydantic_model:: my_package.MyModel
```
The docs build bundles sphinx.ext.autodoc, napoleon, linkcode, and
autodoc_pydantic (see docs/source/_ext/autodoc_subpackages.py), so signatures,
type hints, docstrings, and Pydantic model fields are generated automatically —
and every documented object gets a [source] link to the exact lines on
GitHub, with no per-object URL maintained by hand. (For this to work the
subpackage must be importable in the docs build; jupyter-ai already installs
its subpackages.)
Marking an API contract. To classify a class’s members — what a subclass
must implement vs. what the base class provides — mark each public member with
a documentation marker and render the grouped reference with the
contract-api directive:
```{eval-rst}
.. contract-api:: my_package.MyClass
```
The markers live in a small, dependency-free doc_markers.py module
(jupyter_ai_persona_manager has the reference copy). They are
documentation-only — each mark_* decorator (mark_required,
mark_recommended, mark_optional, mark_subclass_api, mark_consumer_api)
just stamps metadata and returns the function unchanged. The contract-api
directive reads those markers to group members into Required / Recommended /
Optional / Available-to-subclasses / Available-to-consumers sections, each with a
badge and source link. Pair it with a CI test that fails if any public member is
unmarked, so the classification can’t drift from the code. To use this in
another package, copy doc_markers.py over — there is no shared home for it
yet. See jupyter-ai-persona-manager for a complete example.
How it works (infrastructure in jupyter-ai)#
Each subpackage repo is vendored into
jupyter-aias a git submodule undersubmodules/<repo>/, sparse-checked-out todocs/only. The registry of submodules lives insubmodules/manifest.json(a"pypi_name": "org/repo"map), which also serves as a visible map of what Jupyter AI is composed of.How submodules are pinned depends on which version of the site is building, so the docs move the way you’d expect:
On
main(thelatestdocs), each submodule tracks its subpackage’smainbranch. The Update submodule documentation workflow refreshes these pins daily (and on demand) and commits straight tomain, so a subpackage’s newest docs appear onlatestwithout waiting for a release.On a release tag (the
stabledocs), each submodule is frozen to the released tag matching that package’s version range inpyproject.toml(pip / PEP 440 semantics), so a release captures a coherent snapshot. This freeze runs automatically duringjupyter-releaser’s prep step, via theafter-bump-versionhook (scripts/freeze-doc-submodules.sh), and lands in the release commit.
Both paths share one script,
scripts/update-doc-submodules.sh(--mode mainvs--mode release). Read the Docs is configured to servestable— the highest non-prerelease tag — by default, so users see the latest released docs and pre-releases never become the default.On Read the Docs,
scripts/rtd-post-checkout.sh(wired viabuild.jobs.post_checkoutin.readthedocs.yaml) initialises each submodule sparsely todocs/. RTD’s nativesubmodules:support is intentionally not used because it cannot do a sparse checkout.A Sphinx extension,
docs/source/_ext/subpackage_docs.py, runs onbuilder-inited: it copies each submodule’sdocs/source/{contributors,developers}/subtree into a git-ignored staging dir underdocs/source/{contributors,developers}/<repo>/and injects the subpage into the matching aggregation page’s{toctree}. Missing docs are silently skipped.Integration tests in
docs/tests/run a realsphinx-buildagainst synthesized fixtures to verify the aggregation, and the Docs build system CI workflow runs them on any change to the docs build system.
Testing#
Unit and integration tests#
Jupyter AI packages all use pytest for unit and integration tests. Within a
submodule, you can run
just pytest
to run the unit tests for a submodule.
E2E tests#
Jupyter AI packages use Playwright for E2E tests (user-level tests). Galata is used to test JupyterLab extensions.
Install test dependencies (needed only once):
source .venv/bin/activate
cd <submodule>/ui-tests/
jlpm install
jlpm playwright install
Tests involve snapshot comparisons against reference snapshots generated by the Github CI. If you are using an OS other than Linux, you will need to generate local snapshots before running the tests locally for the first time. To do this, execute the command:
source .venv/bin/activate
cd <submodule>/ui-tests/
jlpm test:update
To execute tests, run:
source .venv/bin/activate
cd <submodule>/ui-tests/
jlpm test
Design principles#
Maintainers of Jupyter AI have adopted principles that contributors should also follow. These principles, which build on top of the Zen of Python, are intended to earn users’ trust by keeping their data under their control. The following list is non-exhaustive; maintainers have discretion to interpret and revise these principles.
Jupyter AI is vendor-agnostic. Jupyter AI does not discriminate between agent or model providers. A feature in Jupyter AI may be specific to one agent or model provider if it cannot be used with other models or providers.
Jupyter AI only responds to an explicit prompt; it does not watch files and it does not send prompts automatically. Any change that watches user files must be opt-in only. One form of opt-in is users building agentic personas that undertake non-deterministic actions, including sending prompts to other personas or agents, managing the file system, etc.
Jupyter AI is transparent with its chat prompts. The chat interface and magic commands use system messages and prompt templates that are open source, so that users know what gets sent to language models.
Jupyter AI is traceable; users know when it has been used to generate content. When Jupyter AI generates a notebook, the notebook says that it was generated by Jupyter AI. When a user runs a Jupyter AI magic command in a notebook, output cells say, in their metadata, that they were generated by Jupyter AI.
Jupyter AI uses a human-centered design. The chat interface should look and feel like chat applications that are generally available. The magic commands should look and work like other IPython magic commands. Settings screens should be used minimally, and wherever they are used, they should be readable and understandable, even for users not fluent in the user interface language.
Issues and pull requests that violate the above principles may be declined. If you are unsure about whether your idea is a good fit for Jupyter AI, please open an issue so that our maintainers can discuss it with you.
Subpackages