[ANN] Sessions.jl, A reactive Julia notebook IDE

Sessions.jl

A reactive Julia notebook IDE.

Docs
License: MIT


Warning: Experimental, alpha-quality software. Untested outside its own test suite. Rough edges everywhere. Everything is subject to breaking changes. If you need a reliable reactive notebook today, use Pluto.jl. This exists to explore ideas at the intersection of reactive notebooks, file-based collaboration, and WebAssembly compilation.

What is Sessions.jl?

Sessions.jl is a reactive Julia notebook that runs in your browser as a local web IDE. It is designed around plain .jl files that anyone can edit — you in the browser, an AI assistant from the terminal, or a script in CI. A file watcher picks up all changes and the UI updates live.

Key ideas:

  • File-based collaboration. The notebook is a plain .jl file. Edit in the browser, from the terminal, or programmatically. Changes from any source appear in real time via file watching.
  • Code/state separation. Pure code lives in .jl, cached outputs in .sessions.toml. External edits never corrupt your execution state. Delete the cache anytime; re-run to regenerate.
  • Full IDE in the browser. CodeMirror editor with Julia syntax highlighting, Shoelace file explorer with lazy loading, xterm.js terminal with real PTY shell. Everything in one window.
  • Pluto-compatible. Same .jl file format, same reactivity engine (ExpressionExplorer + PlutoDependencyExplorer). Open the same file in Pluto or Sessions.
  • Integrated terminal. Real PTY-backed shell via xterm.js. Type julia to get a REPL. Run build commands. Multiple tabs. All without leaving the notebook.
  • Runic.jl formatting. Format individual cells or the entire notebook with one click via an isolated Runic.jl subprocess.
  • WASM experiments. Compiling notebook interactivity to WebAssembly via WasmTarget.jl, so exported notebooks can run without a Julia server. Very early, barely works for sliders.

Installation

Requires Julia 1.12+.

using Pkg
Pkg.Apps.add(url="https://github.com/GroupTherapyOrg/Sessions.jl")

This installs the sessions command to ~/.julia/bin/.

Quick Start

# Open a notebook in the web IDE
sessions my_notebook.jl

# Start in a project directory (file explorer shows that directory)
cd my_project/ && sessions

# Run headlessly (CI, scripts, automation)
sessions run my_notebook.jl

The web IDE opens at http://127.0.0.1:8080.

Or from a Julia session:

using Sessions
Sessions.main(["my_notebook.jl"])

Architecture

Sessions.jl/
├── src/
│   ├── Sessions.jl          # Core module
│   ├── types.jl             # Cell, Notebook, CellOutput
│   ├── format.jl            # .jl notebook parser/serializer (Pluto-compatible)
│   ├── analysis.jl          # Reactive dependency analysis
│   ├── kernel.jl            # Cell execution engine
│   ├── session.jl           # .sessions.toml cache read/write
│   ├── formatting.jl        # Runic.jl formatter (isolated subprocess)
│   ├── pty.jl               # PTY management (terminal subprocess)
│   ├── terminal_server.jl   # xterm.js to PTY WebSocket bridge
│   ├── web_server.jl        # WebSocket channel handlers
│   ├── web/                 # Web UI (Therapy.jl app)
│   │   ├── app.jl           # Web app entry point
│   │   └── src/components/  # Layout, NotebookPanel, FileExplorer, ReplPanel, etc.
│   └── worker/              # Malt.jl notebook workers (isolated execution)
├── SessionsUI/              # Lightweight notebook API (zero heavy deps)
│   └── src/
│       ├── SessionsUI.jl
│       └── widgets.jl       # @bind, Slider, Bond, etc.
└── test/

Two Packages, One Repo

Package Purpose Deps How to use
Sessions.jl The IDE app Therapy.jl, Malt.jl, WasmTarget.jl, HTTP… Pkg.Apps.add(url=...)
SessionsUI Notebook API for @bind UUIDs only (stdlib) using SessionsUI: @bind, BoundSlider

SessionsUI is what notebook code imports. It has zero heavy dependencies and compiles in ~300ms. Sessions.jl (the app) depends on SessionsUI, not the other way around.

Code/State Separation

Sessions.jl splits your notebook into two files:

File Contains Role
notebook.jl Cell code, cell order, fold/disabled metadata Source of truth — editable from anywhere
notebook.sessions.toml Cached outputs, stdout, runtime, errors Execution cache, optional, deletable, auto-regenerated

Anyone can freely edit the .jl file — the browser IDE, an external editor, or a script. The file watcher detects changes within a second, marks modified cells as stale, and the UI shows what needs re-execution.

@bind Widgets

using SessionsUI: @bind, BoundSlider, BoundCheckBox, BoundTextField, BoundSelect

@bind x BoundSlider(1:100)
@bind name BoundTextField(default="world")
@bind flag BoundCheckBox()
@bind choice BoundSelect(["A", "B", "C"])

Collaborative Editing

Since notebooks are plain .jl files, they work naturally with any external tool — AI assistants, other editors, scripts, CI pipelines:

  1. Edit in the browser — write and run code in the web IDE
  2. Edit externally — modify notebook.jl from any editor or tool
  3. File watcher detects changes in under a second
  4. UI marks modified cells as stale (orange indicator)
  5. Run Stale re-executes only what changed

The .sessions.toml file caches execution state so reopening a notebook shows previous outputs without re-running everything. The integrated terminal lets you run commands, install packages, or start a Julia REPL without leaving the IDE.

Built On

Experiments

Things being explored, none production-ready, all likely to change or be removed:

  • JETLS integration: real-time JET.jl diagnostics via LSP
  • WASM export: compiling @bind interactivity to WebAssembly so exported notebooks run without a Julia server
  • Malt.jl workers: each notebook tab runs in its own process for isolation

License

MIT

35 Likes

Installing needs

ERROR: expected package `Therapy [b2c3d4e5]` to be registered

and GitHub - GroupTherapyOrg/WasmTarget.jl · GitHub which I all failed to install.

If you want to try again, I think it will work now as long as you are on Julia 1.12. I switched from local paths to GitHub URLs for the dependencies in the Project.toml which I think was the issue:

using Pkg
Pkg.Apps.add(url="https://github.com/GroupTherapyOrg/Sessions.jl")

This is so cool. I’m checking it out now! Thank you for the work and please let me know how I can support things from the Tachikoma side. I have a lot of enhancements about to drop in a 1.1 release, in the next day or two.

2 Likes

One question, is there any way to support interactive table exploration in the terminal — something with a point-and-click feel similar to how Pluto handles DataFrame inspection? The .png rendering for CairoMakie output was great, so I’m curious how far the TUI experience can realistically approach a full Pluto experience. Something a little richer than the current DataTable support that Tachi already provides

Happy to ideate around this, I’m sure there definitely is a way even if it’s not built in quite yet. Maybe connect on that chat?

1 Like

This is so so cool! Can’t wait to try it out. What about something even lighter weight than relying on Pluto? TBH I’ve always found Plutos pros and cons to be… severe. Is the reactivity that huge here?

2 Likes

Fair warning — it might feel clunky right now since the TUI isn’t fully optimized yet. That said, I think once you try it, the two-layer system (.jl file for code, .session.toml for reactivity/state) starts to feel like the best of both worlds.

It’s all very experimental, so I’m hoping to solidify the design through feedback and iteration — something that captures Pluto’s reactivity where it makes sense, without getting in the way during heavy, long-running computations.

One concrete example: I’m not planning to integrate Pluto’s package management ecosystem. Instead, the goal is to build a good enough IDE-like shell directly into Sessions.jl, so you can just use Julia’s native package manager seamlessly.

2 Likes

Starring this! This seems like a nice alternative to Pluto.jl in some respects.

1 Like

Would you be able to include a GIF of this in action on the README or share one here? I’d love to see it working to get a stronger sense of the workflows you are talking about here.

Thanks @Dale_James_Black – exciting work!

This looks nice for working with Pluto on an HPC cluster without setting up a connection for Pluto. Thanks!

I have some questions. How well do agents work with it? Can they easily create notebooks? Does it come with a skill or some mechanism to instruct agents?

In my previous attempts to make them create Pluto notebooks, they have failed to follow the philosophy (one line or block per cell), the style (using let instead of avoiding naming clashes), or they hallucinated cell IDs and order. How do you avoid that?

This is something I’ll be looking at, by connecting Sessions.jl with Kaimon.jl, in the near future. I’ll be looking at how Sessions.jl could expose MCP tools that agents could use to manipulate the notebook in a more directed manner, such as writing the code directly into a cell. Probably will need some experimentation to figure out the right approach.

2 Likes

I just wrote an agent that uses kaimon as it’s repl. It has a TUI interface. I think Sessions.jl style interface would be really cool!

1 Like

Tangential question: what did you use to build the documentation? Looks really cool too!

1 Like

I just added a GIF to the readme (GitHub - GroupTherapyOrg/Sessions.jl · GitHub) – and I will try to attach it in this reply too but not sure if that will work

Sessions.jl demo
1 Like

That is a super prototype framework I am working on. If it ever gets far enough, I will announce it but for now it’s nothing really yet

can’t install it:

~$ julia
        Info The latest version of Julia in the `release` channel is 1.12.5+0.x64.linux.gnu. You currently have `1.12.1+0.x64.linux.gnu` installed. Run:

  juliaup update

in your terminal shell to install Julia 1.12.5+0.x64.linux.gnu and update the `release` channel to that version.
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.12.1 (2025-10-17)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org release
|__/                   |

julia> using Pkg

(@v1.12) pkg> activate --temp
  Activating new project at `/tmp/jl_PdHWdz`

julia> Pkg.Apps.add(url="https://github.com/GroupTherapyOrg/Sessions.jl")
    Updating git-repo `https://github.com/GroupTherapyOrg/Sessions.jl`
    Updating git-repo `https://github.com/GroupTherapyOrg/WasmTarget.jl.git`
    Updating git-repo `https://github.com/GroupTherapyOrg/Tachikoma.jl.git`
    Updating git-repo `https://github.com/GroupTherapyOrg/Therapy.jl.git`
ERROR: Unsatisfiable requirements detected for package Pkg [44cfe95a]:
 Pkg [44cfe95a] log:
 ├─possible versions are: 1.12.0 or uninstalled
 └─restricted to versions 1.12.1 - 1 by Tachikoma [468859d6] — no versions left
   └─Tachikoma [468859d6] log:
     ├─possible versions are: 1.1.0 or uninstalled
     ├─restricted to versions 1 by Sessions [c3d4e5f6], leaving only versions: 1.1.0
     │ └─Sessions [c3d4e5f6] log:
     │   ├─possible versions are: 0.1.0 or uninstalled
     │   └─Sessions [c3d4e5f6] is fixed to version 0.1.0
     └─Tachikoma [468859d6] is fixed to version 1.1.0
Stacktrace:
  [1] check_constraints(graph::Pkg.Resolve.Graph)
    @ Pkg.Resolve ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Resolve/graphtype.jl:1046
  [2] Pkg.Resolve.Graph(compat::Dict{…}, compat_weak::Dict{…}, uuid_to_name::Dict{…}, reqs::Dict{…}, fixed::Dict{…}, verbose::Bool, julia_version::VersionNumber)
    @ Pkg.Resolve ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Resolve/graphtype.jl:350
  [3] deps_graph(env::Pkg.Types.EnvCache, registries::Vector{…}, uuid_to_name::Dict{…}, reqs::Dict{…}, fixed::Dict{…}, julia_version::VersionNumber, installed_only::Bool)
    @ Pkg.Operations ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Operations.jl:705
  [4] resolve_versions!(env::Pkg.Types.EnvCache, registries::Vector{…}, pkgs::Vector{…}, julia_version::VersionNumber, installed_only::Bool)
    @ Pkg.Operations ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Operations.jl:525
  [5] up(ctx::Pkg.Types.Context, pkgs::Vector{PackageSpec}, level::UpgradeLevel; skip_writing_project::Bool, preserve::Nothing)
    @ Pkg.Operations ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Operations.jl:1901
  [6] up
    @ ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Operations.jl:1880 [inlined]
  [7] up(ctx::Pkg.Types.Context, pkgs::Vector{…}; level::UpgradeLevel, mode::PackageMode, preserve::Nothing, update_registry::Bool, skip_writing_project::Bool, kwargs::@Kwargs{})
    @ Pkg.API ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/API.jl:391
  [8] up
    @ ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/API.jl:363 [inlined]
  [9] up
    @ ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/API.jl:169 [inlined]
 [10] #resolve#167
    @ ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/API.jl:397 [inlined]
 [11] resolve
    @ ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/API.jl:396 [inlined]
 [12] (::Pkg.Apps.var"#_resolve##2#_resolve##3"{Pkg.Types.PackageEntry, Base.UUID})()
    @ Pkg.Apps ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Apps/Apps.jl:165
 [13] activate(f::Pkg.Apps.var"#_resolve##2#_resolve##3"{Pkg.Types.PackageEntry, Base.UUID}, new_project::String)
    @ Pkg.API ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/API.jl:1404
 [14] _resolve(manifest::Pkg.Types.Manifest, pkgname::String)
    @ Pkg.Apps ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Apps/Apps.jl:162
 [15] add(pkg::PackageSpec)
    @ Pkg.Apps ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Apps/Apps.jl:213
 [16] add(; name::Nothing, uuid::Nothing, version::Nothing, url::String, rev::Nothing, path::Nothing, subdir::Nothing, kwargs::@Kwargs{})
    @ Pkg.Apps ~/.julia/juliaup/julia-1.12.1+0.x64.linux.gnu/share/julia/stdlib/v1.12/Pkg/src/Apps/Apps.jl:429
 [17] top-level scope
    @ REPL[4]:1
Some type information was truncated. Use `show(err)` to see complete types.

Even though I managed to install it, I couldn’t figure out how to do anything with it.

It doesn’t seem like there’s a keyboard shortcut to run cells. And when I tried to run import Pkg, it just gave me an error “Package Pkg not found in current path.” Thus, I wasn’t able to activate any environment and try out anything else, like plotting.

On top of that, the program seems to take over the terminal with a light-gray on dark-gray color scheme (as in the original screenshots). Please do not override my terminal colors, which are dark on light for good reasons. I will not use terminals with dark backgrounds as these do not meet my accessibility needs.

I would have been interested to find out how well this program handles graphical output, and whether it can handle running inside tmux or over an SSH connection (something that in my experience is extremely tricky, and ultimately let me to write a very hacky MuxDisplay for my own display needs). That experience makes me wonder whether a notebook app that is inherently multi-window/multi-tab might not work better if it was sitting on top of a multiplexer instead of a custom TUI framework

1 Like

Ahh sorry, I had a stupid overly restrictive [compat] sneak into main branch as I have been making continuous tweaks.

I THINK this should now work if you want to try it again, but if it still doesn’t please let me know because I have only tested on mac with iterm so my testing is SUPER limited:

Pkg environment stuff is still not stable as I am trying to avoid some of the built in package management stuff that Pluto provides which means it’s not a direct “copy Pluto 1:1” as it is on some of the reactivity and other backend stuff. That being said, I am working on fixing the package activation issues right now and will hopefully have that pushed soon.

With regard to the color scheme, I would love to have multiple options some day but that is too much work for now at such an unstable and alpha stage.

I am also super interested in *mux and how that may or may not be useful

1 Like