Jupyter-native access to coding agents over the Agent Client Protocol (ACP): per-chat single-agent binding.
Project description
jupyterlab-acp
Jupyter-native access to coding agents over the Agent Client Protocol (ACP) — a per-chat, single-agent experience in JupyterLab, with capability-driven model/mode selectors and slash-command + skill support that comes straight from whatever ACP agent ("harness") you bind a chat to.
Status: early but working. A functioning JupyterLab extension: open a chat in the sidebar (or as draggable main-area tabs), bind an agent, switch models/modes, run slash commands, approve tool calls, and watch it edit your open notebook live. Rough edges remain — see Limitations. The design rationale is in
docs/design-decisions.md.
Install
Requires JupyterLab ≥ 4.2 and at least one ACP agent on your PATH — e.g.
claude-agent-acp (Claude Code) or opencode. Not yet on PyPI, so install from
source (JupyterLab is needed at build time to compile the extension):
git clone https://github.com/SchmidtDSE/jupyterlab-acp
cd jupyterlab-acp
pip install jupyterlab hatchling hatch-jupyter-builder editables # build tooling (jlpm)
jlpm install && jlpm build
pip install -e . --no-build-isolation # hook reuses the built JS
jupyter labextension develop --overwrite .
Use
jupyter lab
- Click the chat icon in the left sidebar for the docked assistant, or New ACP Chat in the launcher to open a chat as a main-area tab (open as many as you like; drag/split them however you want).
- Pick an agent, send a message. The model / mode selectors below the input
reflect what the agent advertises; type
/for its slash commands. - When the agent wants to use a tool, an approval card appears — allow or
reject. Approve a notebook edit and watch the open
.ipynbupdate live.
The idea
A chat is bound to one ACP agent for its life — Claude Code, OpenCode, Gemini CLI, Codex, Goose, and so on. The toolbar then surfaces only what that agent advertises over ACP: its available models, its session modes (plan / accept-edits / …), its config options, and its slash commands. Nothing is hard-coded per harness; the UI is driven by the protocol.
This separates two axes that are usually conflated in AI chat UIs:
- Harness — which agent runtime mediates tool use, file I/O, MCP, and sub-agents.
- Model — which LLM that harness is talking to.
…and it leaves a third axis — persona, the bundle of context you bring to a
conversation — to be expressed the way the open ecosystem already expresses it:
as a Skill or an MCP server the harness loads, not as
a bespoke Jupyter object. The reasoning is in
docs/personas-as-skills.md.
Why it's in Jupyter at all
Because the agent can edit the notebook you have open, live. The harness
writes the .ipynb on disk; JupyterLab's collaborative document layer
(jupyter-server-documents) detects the out-of-band change and reflects it into
your open notebook view within a second or so — no chat-specific machinery
required. That live-document loop is the thing a terminal next to Jupyter can't
give you, and it's the reason this is a JupyterLab extension rather than a
standalone app.
How it's built
jupyterlab-acp is deliberately a thin, additive layer on open standards:
agent-client-protocol— the official ACP Python library (the same one every ACP client builds on). All agent communication goes through it.@jupyter/chat— the JupyterLab chat widget primitives.jupyter-server+ its collaborative document layer — for the live notebook-editing loop above.
It does not depend on, fork, or vendor the jupyter-ai persona/router stack.
Its dependency graph is part of the argument: a Jupyter-native ACP experience
needs none of the persona abstraction. See
docs/design-decisions.md (Fork 6) for the full
reasoning.
Relationship to Project Jupyter and to Zed
This is an independent, community project — not an official Project Jupyter package, and not affiliated with or endorsed by Zed Industries.
The per-chat single-agent model is inspired by Zed, which co-created ACP and pioneered this interaction design for external agents. ACP is Zed's open protocol, released for exactly this kind of adoption; building on it is how we honor that work. We're grateful for it.
Reference implementation
A working proof-of-concept — the same idea built inside a fork of
jupyterlab/jupyter-ai, on top of the jupyter-ai stack — lives at
cboettig/jupyter-ai@acp-bridge-impl.
jupyterlab-acp is the ground-up redesign that sheds those dependencies; the PoC
remains a useful empirical reference for how each piece behaves.
Context
This work is part of an ongoing conversation with the Jupyter AI team about how
personas, models, and agent harnesses should relate:
jupyterlab/jupyter-ai#1558.
Limitations
Early and honest about it:
- Not on PyPI yet — install from source (above).
- One conversation per panel/tab — no in-panel thread switcher yet (open multiple tabs instead).
- Minimal rendering — agent text + tool-approval cards are shown; rich tool-call/diff and reasoning rendering, and a config-options selector, are not built yet.
- Auth — REST routes are token-authenticated; the streaming websocket is not yet hardened. Run locally.
Development
# Python env (uv or venv); install package + tooling
pip install jupyterlab
jlpm install && jlpm build # dev build (tsc + labextension)
pip install -e ".[test]"
jupyter labextension develop --overwrite .
python -m pytest # 37 tests: ACP core, capabilities, binding,
# handlers (real-server), serializer, permission
jlpm build:lib # typecheck the frontend
Layout: jupyterlab_acp/ is the Python server extension (ACP session/binding +
REST/websocket handlers, on agent-client-protocol); src/ is the TypeScript
labextension (React chat panel); tests/ and validation/ hold the suite and
the Step-0 notebook-reflection check.
License
BSD 3-Clause. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jupyterlab_acp-0.0.1.tar.gz.
File metadata
- Download URL: jupyterlab_acp-0.0.1.tar.gz
- Upload date:
- Size: 86.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f64eaaed6d346bc95b8824ffa8a8baca84f48bf1d4a625d5a3311f56389981e
|
|
| MD5 |
81f0f7d19f10834843a02da113530f0a
|
|
| BLAKE2b-256 |
a45f541f7249827dbb6587adc1ec838d8fb61fa1ddc71e7886020294af64128b
|
Provenance
The following attestation bundles were made for jupyterlab_acp-0.0.1.tar.gz:
Publisher:
release.yml on SchmidtDSE/jupyterlab-acp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jupyterlab_acp-0.0.1.tar.gz -
Subject digest:
6f64eaaed6d346bc95b8824ffa8a8baca84f48bf1d4a625d5a3311f56389981e - Sigstore transparency entry: 1730094837
- Sigstore integration time:
-
Permalink:
SchmidtDSE/jupyterlab-acp@83b459af43afb1a347951c36ded49d191dd10b92 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/SchmidtDSE
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@83b459af43afb1a347951c36ded49d191dd10b92 -
Trigger Event:
release
-
Statement type:
File details
Details for the file jupyterlab_acp-0.0.1-py3-none-any.whl.
File metadata
- Download URL: jupyterlab_acp-0.0.1-py3-none-any.whl
- Upload date:
- Size: 68.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3da3c622129aae8e6e832ab312d0b2456930d813b7c927ceb6488ecc96f22488
|
|
| MD5 |
ed74ef862308a83c2900b6b50d9efee9
|
|
| BLAKE2b-256 |
af4940bf13b082ae09eeaa513e5eee310d94210d3d110f231475325eb6990623
|
Provenance
The following attestation bundles were made for jupyterlab_acp-0.0.1-py3-none-any.whl:
Publisher:
release.yml on SchmidtDSE/jupyterlab-acp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jupyterlab_acp-0.0.1-py3-none-any.whl -
Subject digest:
3da3c622129aae8e6e832ab312d0b2456930d813b7c927ceb6488ecc96f22488 - Sigstore transparency entry: 1730095202
- Sigstore integration time:
-
Permalink:
SchmidtDSE/jupyterlab-acp@83b459af43afb1a347951c36ded49d191dd10b92 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/SchmidtDSE
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@83b459af43afb1a347951c36ded49d191dd10b92 -
Trigger Event:
release
-
Statement type: