@adamancyzhang/agent-py-debugger
v0.2.1
Published
Python breakpoint debugging CLI for AI agents — JDWP-style remote debug: breakpoints, stack, locals, eval and stepping over TCP. PyCharm-like, driven entirely from the command line.
Maintainers
Readme
agent-py-debugger
Breakpoint debugging for Python services from the command line — no IDE required. Built for AI agents: stable machine-readable output, plus a human-readable text format.
What it does
agent-py-debugger debugs Python processes the way JVM remote debugging (JDWP) works: the target starts with a debug listener and the CLI connects over TCP. A pure-stdlib trace engine (sys.settrace / threading.settrace) runs inside the target process — breakpoints, pause state, and the protocol server all live there — so every CLI invocation is a stateless one-shot connection, exactly what an agent tool layer needs.
agent-py-debugger connect 127.0.0.1:5678 # open a session against a listener
agent-py-debugger breakpoint set app/api/users.py 120
agent-py-debugger wait # block until a thread pauses
agent-py-debugger locals --depth 2 # local variables (in-memory data)
agent-py-debugger eval "token_pair.expires_in" # evaluate any expression
agent-py-debugger continue # release the paused threadThe debugged process can be anywhere — any OS, any CPU. Only the machine running the CLI needs Python 3.8+.
Installation
Global (recommended)
npm install -g @adamancyzhang/agent-py-debuggerRequires Node >= 18 (launcher shim only) and Python 3.8+ (the CLI itself is pure Python — no pip packages needed; the interpreter is discovered automatically, override with AGENTDBG_PYTHON).
From source
git clone https://github.com/adamancyzhang/agent-py-debugger.git
cd agent-py-debugger
npm linkAs a dependency of the target project
The debug listener ships as a Python package; add it to the target's environment so the app can be started with debugging enabled:
# pyproject.toml of the target project
dependencies = [..., "agentdbg>=0.1.1"]
[tool.uv.sources]
agentdbg = { path = "/path/to/agent-py-debugger" } # or a git URL / PyPIThen uv sync (or pip install -e .). Equivalent without any package manager:
python -m agentdbg install /path/to/venv/lib/python3.12/site-packagesEnabling debugging in the target
The target process must be started with the debug listener — one of three equivalent ways:
# ① CLI wrapper — no configuration changes. Non-blocking by default (suspend=n):
agent-py-debugger run --port 5678 -- -m uvicorn app.main:app --host 0.0.0.0 --port 3003
# --suspend: suspend=y — wait for `agent-py-debugger resume` before running
# --foreground: stay in the foreground (Ctrl-C stops, like plain uvicorn)
# ② Environment variable + sitecustomize — docker/systemd friendly.
# The wheel installs a sitecustomize.py that is a strict no-op unless
# AGENTDBG_LISTEN is set:
AGENTDBG_LISTEN=127.0.0.1:5678 AGENTDBG_TOKEN=secret .venv/bin/uvicorn app.main:app --port 3003
# AGENTDBG_SUSPEND=1 = suspend=y · AGENTDBG_AUTO_CONTINUE=300 · AGENTDBG_LOG=/tmp/dbg.log
# ③ Explicit in code (the debugpy.listen equivalent):
# import agentdbg.client as c
# c.listen(host="127.0.0.1", port=5678, token="secret")JVM mapping: run (default) = suspend=n — the app runs completely normally while no debugger is connected, and a debugger can connect or disconnect at any time. disconnect resumes every paused thread and keeps the listener and breakpoints (JVM disconnect semantics); detach removes tracing entirely and shuts the listener down. Neither stops the process: a target started with run is owned by agent-py-debugger and is stopped with kill --session <port>; a target started outside agentdbg (options ② / ③) is its owner's to stop — agentdbg never kills it.
Quick Start
agent-py-debugger connect 127.0.0.1:5678 --token secret # session (token optional if one exists)
agent-py-debugger breakpoint set app/auth/router.py 60
agent-py-debugger breakpoint set router.py:60 --condition 'body.email.endswith("dev")'
agent-py-debugger breakpoint list / delete 1 / enable 1 / disable 1
curl -s http://localhost:3003/api/... & # drive the app; the request hangs at the breakpoint
agent-py-debugger wait --timeout 30 # block until a thread pauses
agent-py-debugger status # paused threads + reason + breakpoint id
agent-py-debugger stack # call stack with file:line and code line
agent-py-debugger locals --depth 2 # local variables, recursive type/length/truncation
agent-py-debugger eval "token_pair.expires_in"
agent-py-debugger step --over | --into | --out
agent-py-debugger continue # release the request
agent-py-debugger disconnect # resume everything, end the interaction
agent-py-debugger kill --session 5678 # done with a 'run'-launched target: stop it, clean session + logs
agent-py-debugger detach # externally-started target only: remove tracing, process keeps runningCommands
connect
agent-py-debugger connect <host:port> [--token T]Verifies the listener and records a session in ~/.agentdbg/<name>.json (0600, token inside). Subsequent commands reuse the latest session; select one with --session <name> or AGENTDBG_SESSION.
run
agent-py-debugger run [--port N] [--token T] [--suspend] [--foreground] [--auto-continue S] -- <python> <script | -m module> [args...]Launches a new process with the listener enabled. Non-blocking by default: the command returns as soon as the listener is up and the app keeps running (logs: ~/.agentdbg/run-<token>/app.log). The interpreter may be omitted (-m uvicorn … or a .py path are auto-detected). The target keeps running across connect/disconnect/detach — stop it with kill when you are done with it (below).
breakpoint
agent-py-debugger breakpoint set <file> <line> | file:line [--condition E] [--temporary] [--disabled]
agent-py-debugger breakpoint list | delete <id> | delete --all | enable <id> | disable <id>Paths are realpath-normalized, symlinks fine. Setting a breakpoint warns when the line carries no bytecode (it would never hit). Conditional breakpoints evaluate the expression in the hit frame; a failing condition is reported on the breakpoint instead of stopping.
wait
agent-py-debugger wait [--timeout S]Blocks until a thread pauses (or the timeout). Exit code 2 means "no pause within the timeout" — the agent can branch on it.
status / stack / locals / globals / eval
agent-py-debugger status
agent-py-debugger stack [--thread T]
agent-py-debugger locals [--thread T] [--frame I] [--depth D] [--all]
agent-py-debugger globals [--thread T] [--frame I] [--depth D] [--all]
agent-py-debugger eval <expr> [--thread T] [--frame I] [--timeout S]locals/globals produce recursive descriptions with type, length and truncation — the Variables-view equivalent. eval runs in the paused frame with a timeout (user expressions that hang are contained). --frame selects any frame of the paused thread's stack.
step / continue / disconnect / detach
agent-py-debugger step --over | --into | --out [--thread T]
agent-py-debugger continue [--thread T | --all]
agent-py-debugger disconnect
agent-py-debugger detachPyCharm stepping semantics. A breakpoint triggers once per execution pass — the CPython after-call line event does not re-trigger it, loop iterations do. disconnect = JVM disconnect (resume everything, listener + breakpoints kept, reconnect anytime). detach removes tracing and shuts the listener down — it is only accepted for targets that were started outside agentdbg run; on a run-managed target it is refused, because the process would be left running with no owner.
kill
agent-py-debugger kill --session <name> | --pid <pid> [--json]Stops a target that agentdbg run launched: SIGTERM, SIGKILL escalation after ~5s, then removal of the session file and the ~/.agentdbg/run-<token>/ workdir. The name is a session name from agentdbg sessions (the listener port); --session is explicit — kill never falls back to the latest session. --pid is the rescue path for targets whose session file was lost (orphans from an interrupted session).
Ownership is verified against the live process: only a pid running under an agentdbg run bootstrap (~/.agentdbg/run-*/bootstrap.py) is ever killed. A session recorded via connect — a service started with AGENTDBG_LISTEN/install, a process on another machine — is refused with an error: stopping those services belongs to their owner, and detach remains the way to end debugging without touching the process.
sessions / repl / install / resume
agent-py-debugger sessions
agent-py-debugger repl
agent-py-debugger install <site-packages-dir>
agent-py-debugger resumesessions lists recorded sessions; repl is an interactive prompt for humans; install copies the package + sitecustomize into a venv without any package manager; resume releases a suspend=y target.
Agent Mode
--json prints a single-line envelope on stdout — errors included:
{"ok":true,"paused":true,"paused_threads":[{"thread_id":8451547520,"thread_name":"MainThread",
"reason":"breakpoint","breakpoint_id":1}],"threads":[…],"breakpoints":2,"suspended":false,"pid":16283}Exit codes: 0 success / 1 protocol error / 2 wait timed out without a pause. Every command accepts --json; the pause/inspection responses are stable objects safe for jq.
Limits & Caveats
- One listener per process; after
detacha minimal per-call hook remains until the process restarts. - A
runtarget never stops itself —disconnect/detachleave the process running by design (JVM model). Stop it withkillwhen done; aruntarget that is only ever detached becomes an orphan. Targets started outside agentdbg keep running under their own owner. - One thread paused, others running — suspend-all is a future version. Use
--threadwhen stepping multi-threaded programs. - Tracing overhead: while the listener is active the target runs under a trace hook (dict-lookup hot path; the same cost class as PyCharm's debugger).
detachremoves it. - Raw threads created via
_thread.start_new_threadare not traced;threading.Threadand asyncio code paths are fully covered. - A dead agent session cannot freeze the app forever if the target was started with
--auto-continue(recommended for production-like targets); pauses always outlive CLI disconnects, so reconnect + continue always works. - Trusted networks only: the listener binds a configurable address and requires the session token.
Skill for AI Coding Assistants
Install the agent-py-debugger skill with the skills CLI, directly from GitHub:
npx skills add adamancyzhang/agent-py-debuggerThe skill is fetched from this repository (skills/agent-py-debugger/SKILL.md), so it stays up to date automatically. Works with Claude Code, Codex, Cursor, Gemini CLI, and other skills-aware assistants. Do not copy SKILL.md from node_modules — it will become stale.
Manual install for Claude Code:
mkdir -p .claude/skills
cp -r skills/agent-py-debugger .claude/skills/Development
python3 tests/e2e.py # end-to-end suite: run/suspend/install modes, breakpoint flow,
# after-call no-retrigger regression, connect reliability, kill lifecycle
npm pack --dry-run # verify the npm tarball
uv build # build the python wheel (sitecustomize included)Layout: agentdbg/ (the Python package: client.py trace engine + protocol server, cli.py commands, connect.py sessions/installation, format.py memory formatting) · bin/ (npm launcher shim) · skills/ (agent skill docs) · tests/ (scratch target + e2e suite) · sitecustomize.py (AGENTDBG_LISTEN bootstrap, shipped by the wheel).
Roadmap
- Suspend-all pause mode (PyCharm "pause all threads")
- Tracepoint breakpoints (log and continue)
- Exception breakpoints
- Windows raw-thread coverage via a C-level hook
License
MIT © 2026 Adamancy Zhang
