I spent today setting up Molten.nvim with Jupyter kernels inside a Nix-managed environment.
What started as “I want inline code execution in Neovim” turned into a deeper look at how Jupyter actually works.
Why I wanted this
Typical data workflow:
- write code
- switch to notebook
- run cell
- inspect output
- switch back
- repeat
It works, but it breaks focus.
I wanted notebook-style interactivity without leaving Neovim.
Molten.nvim
Molten isn’t a Python runner.
It’s a Jupyter client inside Neovim.
So instead of switching tools, I can run:
# %%
import pandas as pd
df = pd.read_csv("sales.csv")
df.head()
and see output inline in the editor.
That changes the workflow:
- write notes
- run analysis
- inspect results
- continue writing
all in one place.
How it works
Molten talks to a Jupyter kernel over the standard protocol:
Neovim
→ Molten.nvim
→ Jupyter protocol (ZeroMQ)
→ Jupyter kernel
→ Python environment
The kernel runs as a separate process and keeps state in memory.
So this:
# %%
sales = df.groupby("Category")["Sales"].sum()
can later become:
# %%
sales.plot()
without restarting anything.
Same architecture used by:
Jupyter Lab VS Code Jupyter extension Molten.nvim
Why NixOS made this better
Instead of manually installing packages:
pip install pandas matplotlib ipykernel
I define the environment in Nix:
pkgs.python3.withPackages (ps: with ps; [
pynvim
ipykernel
pandas
numpy
matplotlib
])
Then wire it into a Jupyter kernel using Home Manager & symlink at user:
# Symlink a reproducible Python environment to ~/.venv/molten
home.file.".venv/molten" = {
source = pkgs.python3.withPackages (ps: with ps; [
pynvim ipykernel pandas numpy matplotlib
]);
};
Finally, register it in my system config, and :MoltenInit picks it up automatically.*
# Register it as a Jupyter kernel so :MoltenInit can find it
home.file.".local/share/jupyter/kernels/molten/kernel.json" = {
text = ''
{
"argv": [
"~/.venv/molten/bin/python3",
"-m",
"ipykernel_launcher",
"-f",
"{connection_file}"
],
"display_name": "Python 3 (molten)",
"language": "python"
}
'';
};
Everything becomes reproducible from a Git clone + rebuild.
One thing that can be confusing at first is why the Jupyter kernel uses a separate Python environment instead of just using whatever project venv you’re already in.
The key idea is that a Jupyter kernel is just a long-running Python process. It starts once, stays alive in memory, and executes code cell by cell. Because of that, it needs a stable, consistent environment to run inside.
In practice, that means it needs a fixed Python interpreter with the required packages already available - that’s what the kernel.json is doing
Learning through AI
I used opencode heavily while setting this up.
Not just to generate config, but to understand what was happening.
The useful part wasn’t getting it working — it was figuring this out:
- why Molten needs a bridge Python
- how Jupyter kernels are discovered
- why state lives in the kernel process
- how Nix rebuilds environments
Now the system actually makes sense, even if i can't write the code.
How this post came together
I didn’t write this directly.
I used opencode to work through setup issues wrote a recap note in Obsidian (inside Neovim) turned that into this post
Flow:
experiment
→ debug with opencode
→ write notes
→ turn notes into post (once i understand what happened!)
It helped turn a messy setup process into something I can actually explain.
Final thoughts
I thought I was just configuring a Neovim plugin.
I ended up learning more about Jupyter, processes, and reproducible environments than I expected.
Molten.nvim sits in an interesting place between notebooks and traditional development.
For data work, keeping analysis, notes, and execution in the same editor feels more natural than I expected.