Skip to main content

A DAP multiplexer that lets you debug from a REPL while your editor shows where you are.

Project description

dap-mux

Every terminal debugger forces a choice: a REPL with full evaluation power but no source context, or an editor with visual breakpoints but a crippled debug console. dap-mux removes the need to choose.

Connect your editor and your REPL to the same debug session simultaneously. Step from your REPL while your editor tracks the current line. Evaluate arbitrary expressions in the stopped frame. Set breakpoints from either side.

┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│    Helix      │◄──DAP──►│              │◄──DAP──►│   debugpy    │
│  source view  │         │   dap-mux    │         │  (or any     │
│  breakpoints  │         │              │         │   DA server) │
└──────────────┘         │              │         └──────────────┘
                          │              │
┌──────────────┐         │              │
│  your REPL    │◄──DAP──►│              │
│  evaluation   │         └──────────────┘
│  stepping     │
└──────────────┘

dap-mux is a DAP proxy. It sits between a debug adapter and multiple clients, routing requests, broadcasting events, and replaying session state to clients that join late. Your editor and your REPL are both first-class DAP clients sharing one session through the multiplexer.

The bundled REPL frontend is an IPython extension — a debug control surface that gives IPython's %magic interface DAP connectivity. It's the REPL the author uses. Other REPLs can connect to the multiplexer too; they just don't have a built-in frontend yet.

Goals

Your editor + your language + your debugger + your REPL — and anything else you want to connect. Your tool choices should compose freely. dap-mux is the connector; the tools are yours.

The commitments that follow from this:

Standard DAP at every boundary. Any DAP-capable editor, REPL, or tool connects to the client-facing interface without special plugins or configuration. Any standard DAP adapter works upstream. Every connection point speaks the standard.

Every connected client is first-class. All clients can perform any standard DAP operation: set breakpoints, step, evaluate expressions, inspect the call stack, select frames. No client is read-only.

Sessions survive client changes. Connecting or disconnecting a client never interrupts the session or restarts the adapter. Connect a second editor mid-session. Disconnect the REPL and reconnect. The session continues.

Late joiners see current state. A client connecting after initialization receives the initialized handshake and, if stopped, the current stop position. An editor joined mid-session immediately shows the correct line.

Status

v0.9.0 is the first public release — the first time it has been possible for anyone other than the author to run this tool. Until now it has been developed and tested privately.

The core mechanics — protocol framing, sequence rewriting, multi-client routing, event broadcasting, late-join state replay — have been tested live and are covered by a test suite. The workflow it enables is real: connect Helix or VS Code and an IPython REPL to the same debugpy session and debug from both simultaneously.

That's two editors, one debug adapter, one REPL, on two platforms. The goals call for any editor, any language, any adapter — and most of that territory has never been touched by anyone. There is much to find and fix. Bug reports, notes from people testing other combinations, and contributions that expand the proven ground are exactly what this project needs right now.

Requirements

uv — it manages the Python runtime automatically.

debugpy must be available in the target environment. dap-mux connects to it over TCP and never imports it directly:

pip install debugpy    # in your project's virtualenv

Installation

uv tool install git+https://github.com/dap-mux/dap-mux --with debugpy

For development:

git clone https://github.com/dap-mux/dap-mux
cd dap-mux
uv sync --group dev

Quick Start

This example uses Helix and the built-in IPython frontend. Any DAP-capable editor works — see Editor Setup.

1. Start the session

dmux script.py

dap-mux spawns debugpy, connects the multiplexer, and opens an IPython REPL:

dap-mux listening on 127.0.0.1:5679
Connect your editor to 127.0.0.1:5679

The IPython prompt appears. The script is paused — it won't start running until an editor client sends configurationDone.

2. Set breakpoints in Helix, then connect

Open the script in Helix and set a breakpoint on the line you want to pause on (<space>b or your configured key). Then connect:

:debug-remote 127.0.0.1:5679 attach

Set breakpoints before connecting. When Helix connects it sends configurationDone, which starts the script. With no breakpoints, the script runs to completion before you can do anything.

Execution starts and pauses at your breakpoint. Helix highlights the current line. IPython prints the stop location.

3. Debug from IPython

%bt              # call stack
%eval results    # evaluate expression in the stopped frame
%frame 2         # switch to a different stack frame (%eval follows)
%n               # step over
%s               # step into
%c               # continue
%finish          # step out
%break script.py:42   # set a breakpoint

Bare Python at the IPython prompt runs locally. %eval evaluates in the debuggee's frame.


Usage

Launch mode

dmux script.py

Spawns debugpy attached to script.py, starts the multiplexer, opens the IPython REPL. Everything in one command.

Attach mode

When debugpy is already running:

dmux --attach 5678
dmux --attach 192.168.1.10:5678

The IPython REPL connects to the existing session. Use %sync to discover the current stopped state if the session was already paused when you joined.

Headless mode

Run the multiplexer without the REPL — useful for scripted setups or when connecting an external IPython:

dmux script.py --no-repl
dmux --attach 5678 --no-repl

CLI reference

dmux [TARGET] [OPTIONS]

Arguments:
  TARGET                   Python script to debug (launch mode)

Options:
  --attach, -a  TEXT       Attach to a running debug adapter ([host:]port)
  --mux-port, -p  INT      Port for clients to connect to (0 = auto)  [default: 0]
  --log-level, -l  TEXT    Log level: DEBUG, INFO, WARNING, ERROR  [default: WARNING]
  --log-file  TEXT         Also write logs to this file
  --no-repl                Start without the IPython REPL
  --version, -V            Show version and exit

Editor Setup

Helix

Add to ~/.config/helix/languages.toml:

[[language]]
name = "python"

[language.debugger]
name = "debugpy"
transport = "tcp"
command = "python3"
args = ["-m", "debugpy"]
port-arg = "--listen=127.0.0.1:{}"

[[language.debugger.templates]]
name = "launch"
request = "launch"
completion = [{ name = "script", completion = "filename" }]
args = { mode = "debug", program = "{0}" }

[[language.debugger.templates]]
name = "attach"
request = "attach"
completion = []
args = {}

Connect to a running dap-mux with :debug-remote host:port attach.

A working copy of this configuration is in demos/helix/.

VS Code

Install the Python Debugger extension (ms-python.debugpy), then add to .vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Connect to dap-mux",
            "type": "debugpy",
            "request": "attach",
            "connect": {
                "host": "127.0.0.1",
                "port": 5679
            }
        }
    ]
}

Start dap-mux with a pinned port (dmux script.py -p 5679) so the launch config can hardcode it. Set breakpoints in VS Code before running the configuration — launching it sends configurationDone and starts execution.

A working copy of this configuration is in demos/vscode/.

Neovim

Configure nvim-dap to connect to the mux port as a DAP server. Point dap.adapters at 127.0.0.1:<mux-port> with type = "server".

Other editors

Any editor with a DAP client works. Configure it to connect to 127.0.0.1:<mux-port> as an existing DAP server — dap-mux speaks standard DAP, no special configuration needed.


IPython Extension

dap-mux ships with an IPython extension that turns IPython into a debug control surface. Load it with %load_ext dap_mux, or use dmux which loads it automatically.

Magic Alias Description
%connect [host:]port Connect to the multiplexer
%disconnect Disconnect
%sync Discover current stopped state (useful after late-joining a paused session)
%bt Call stack
%frame N Select stack frame; subsequent %eval evaluates in that frame's scope
%eval expr Evaluate expression in the current frame
%step %s Step into
%next %n Step over
%continue_ %c Continue execution
%finish Step out of current function
%break file:line [cond] Set breakpoint (optional condition)
%clear file:line Remove all breakpoints in file

%eval evaluates in the debuggee's frame — it has access to the local and global variables at the current stop point. Regular IPython expressions evaluate locally.


How It Works

Each DAP client (editor, REPL) connects to dap-mux over TCP. The multiplexer rewrites sequence numbers so all clients share a single upstream connection to the debug adapter. Responses are routed back to the client that made the request. Events are broadcast to all connected clients.

When a client joins after the session is already initialized, dap-mux replays the cached initialized event and, if the session is currently stopped, the last stopped event — so the late joiner sees current state immediately without requiring an adapter restart.

dap-mux is written in Python. The tool is a network I/O router — it reads JSON from one TCP connection and writes it to others — and Python's asyncio is purpose-built for exactly this. The actual performance ceiling is human keystroke speed; the multiplexer will never be CPU-bound. The IPython integration is the other reason: it runs in the same process, with direct access to IPython's internals. A Go or Rust implementation would have to shell out to Python and do IPC to achieve the same result, trading a clean in-process design for a messy out-of-process one.


Who This Is For

  • Terminal-first developers using Helix, Neovim, Emacs, or any DAP-capable editor who want IDE-quality debugging without leaving the terminal
  • Data scientists who live in IPython and want visual source tracking while debugging
  • Remote developers debugging over SSH where GUI IDEs are impractical
  • Anyone who has wished their debug REPL had tab completion, history, and the ability to import things

Compatibility

Editors

Any DAP-capable editor works as a display client — connect it to the mux port like any other DAP server.

Editor DAP integration Status
Helix Built-in Tested
VS Code Built-in Tested
Neovim nvim-dap Untested
Emacs dap-mode Untested
Vim Vimspector Untested

Languages

The REPL + editor workflow is richest for languages with a capable interactive REPL. The multiplexer itself works with any DAP adapter.

Language Debug adapter REPL
Python debugpy IPython ← tested
Ruby debug gem IRB, Pry
Julia DebugAdapter.jl Julia REPL
Elixir ElixirLS IEx
JavaScript js-debug Node.js REPL

Languages with strong DAP support but no meaningful REPL — Go (Delve), Rust (codelldb), C/C++ (lldb-dap) — still benefit from dap-mux for multi-editor sessions and reconnection without restarting.

dap-mux is tested against debugpy. Other adapters should work (DAP is a standard protocol) but are unvalidated.

Platforms

Platform Status
Linux Tested
macOS Tested
Windows Intended; not yet validated

What this is not

dap-mux is a router, not a debugger. It forwards DAP messages between your tools and your debug adapter. It does not execute code, inspect memory, or understand the state of your program. Everything it connects already does those things — dap-mux provides the connectivity, not the capability.

It does not add debugging features your adapter doesn't already have. If your debug adapter doesn't support something, dap-mux won't supply it. The power comes from the tools you bring. dap-mux connects them.

Terminal-first by design. There is no GUI and there won't be — and on the command line, the only real interaction is starting it. After that, you're working in your editor and your REPL, not in dap-mux.

The IPython extension is Python-specific. The multiplexer works with any language that has a DAP adapter. The bundled REPL integration is built on IPython and is Python-only. Other language REPLs are possible frontends, but they aren't built in.

dap-mux is one component in a pipeline, not an all-in-one tool. If you want a self-contained TUI debugger with its own UI, pudb is excellent and actively maintained. dap-mux is for a different way of working: your editor does one thing well, your REPL does one thing well, your debug adapter does one thing well — dap-mux connects them. Unix has always worked this way.

Limitations

  • Tested with debugpy only. Other debug adapters should work but haven't been validated.
  • Windows support is untested. The code has no known platform-specific dependencies, but it hasn't been validated on Windows yet.
  • No PyPI release yet. Install from source via uv tool install git+....

License

MIT

Contributing

Issues and feedback welcome. The project is young — bug reports and notes on adapters or editors you've tested are especially useful.

See CONTRIBUTING.md for how to set up a development environment, what makes a good PR, and what the project will and won't accept. See CHANGELOG.md for what has changed.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dap_mux-0.9.0.tar.gz (60.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dap_mux-0.9.0-py3-none-any.whl (30.1 kB view details)

Uploaded Python 3

File details

Details for the file dap_mux-0.9.0.tar.gz.

File metadata

  • Download URL: dap_mux-0.9.0.tar.gz
  • Upload date:
  • Size: 60.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for dap_mux-0.9.0.tar.gz
Algorithm Hash digest
SHA256 172170013c43b2e730224c8ef9037086bfdc55dfd11fb5982fdf39168c8f8287
MD5 4ffcdf701b08ce3f82ef71cfb56e5628
BLAKE2b-256 741c63568b3266578dfcc569846e3bcab4fbea45d34034f0112dcfb315b3f495

See more details on using hashes here.

File details

Details for the file dap_mux-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: dap_mux-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 30.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for dap_mux-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3bc4adc46c94433d2b804e3d883ebfe7ded38b6b7bb74f62d7f5c3022aa05d47
MD5 06c84815aefd2d5ce8a7ca331524bca0
BLAKE2b-256 a887e862128a0141621fa402814006dbda0baff659322a4f2154aa5820fd753e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page