When to give an agent a debugger
QuorX's agent can now set breakpoints and read variables in nine languages on Windows. It only gets that debugger after plain debugging has failed. Here is why, how the debug tool works, and how much of it is proven on a real PC.
A debugger seems like an obvious tool to give a coding agent. A person stuck on a bug sets a breakpoint, looks at the variables, and sees what the program actually does instead of guessing. An agent can do the same: QuorX's debug tool starts a program paused, sets breakpoints, steps, and reads the stack and the local variables.
But QuorX doesn't hand the agent that tool at the start of a reply. By default, the agent first has to try the plain way, and the debugger opens only after that has failed. This article explains that choice, what the debugger covers now, and what we have and haven't proven.
The evidence is mixed
The research we based this on is public. In debug-gym (arXiv 2503.21557), a benchmark where agents fix bugs in Python programs, a strong model in that study solved more tasks with Python's debugger available: 37.2% without it, 48.4% with it from the start, and 52.1% when it was unlocked only after five failed attempts to fix the code. Weaker models in the same study did somewhat worse when they were given the debugger from the start.
Two readings follow from that, and QuorX acts on both:
- A debugger helps most when the cheap approach has failed. Reading the error, opening the file at the line it names and adding a print statement solves most bugs faster and with fewer steps. A debugger session costs many tool calls: start, break, continue, read the state, step, read again.
- A debugger can distract a weaker model. Given the tool from the start, a model can spend its steps driving the debugger instead of reading the code. QuorX is built to run on cheap models, so this risk matters more here than for a harness that always uses the strongest model.
We have not measured either effect inside QuorX. The gate below is a reasonable reading of outside research, and it is a setting that can be changed.
The gate
When the agent asks to start or attach the debugger, QuorX checks whether it has "earned" it in this reply. It has when any of these is true:
- two tool calls in this reply came back with an error;
- QuorX's compiler checks sent the work back to be fixed;
- the reviewer sent the build back;
- or the debugger is set to Always in ⚙ Settings → Safety & commands.
Otherwise the tool answers: "Not yet: reproduce the bug, read the error's file and line, and add a print or log line to test your guess first. The debugger opens after a couple of failed tries (the user can turn it on for every reply)."
The setting has three values: After failed tries (the default), Always and Off. Our gate is looser than debug-gym's "after five failed attempts": two errors are enough. That threshold is a judgment call; we have not measured which number works best.
How the debug tool works
Two details matter for safety and for the user's attention:
- The debugger is not a back door. Starting a session runs a real command: a build, an adapter, the program. That command is judged by the same rule engine as everything else, and the card shows it. When Delve is missing, for example, the agent can offer
go install …dlv@latest, which installs into the Go folder for every project, so it asks first. - Debugging stops by itself where a program fails. Delve stops on an unrecovered panic, LLDB on a crash, and Rust programs on every panic. A crash shows where it happened, even with no breakpoint there.
What it covers now
When PR #3 merged on 20 October, the debug tool knew two languages: Node, through V8's inspector, and Python, through debugpy. PR #3 had just rebuilt the Debug Adapter Protocol client so any adapter can plug in. Five pull requests added seven more languages on Windows and Linux, and a sixth added Swift and Objective-C for Macs.
Counted from debugLanguages() in server/debugger.js at commit 0174105 (the PR #3 merge) and today. Linux has the same nine as Windows; a Mac adds Swift and Objective-C.
Some of these needed more than plugging in:
- Java and Kotlin had no adapter that is easy to install. Microsoft's Java debugger runs only as a plugin inside an Eclipse language server. QuorX ships a small adapter of its own, written against the JDK's debugger interface, which the JDK runs straight from its source file. Any computer with a JDK has the debugger.
- Dart only speaks the protocol over its own input and output. QuorX runs it behind a small bridge that listens on the session's port, as part of the same job, so Stop still ends it. The same bridge works for
lldb-dapin that mode. - Electron is attached to: the agent starts the app with
--inspectas a background job and attaches to its main process. Stopping the session then only disconnects; the app keeps running until the user or the agent stops it.
What is proven, and what isn't
Not every debugger has run against a real toolchain on the Windows PC these tests ran on. Here is where each one stands, from each PR's "tested here" section:
| Language | Level | Debugger | How QuorX starts it | Tested how |
|---|---|---|---|---|
| Node (and Electron's main process) | Proven on this PC | V8's inspector, built into Node | runs the file paused, or attaches to a job started with --inspect | real Node; a real Electron 44.9 app (PR #8) |
| Python | Proven on this PC | debugpy | in the project's venv when it has one | a venv with debugpy 1.8.22, stopping in a tkinter button handler (PR #14) |
| Java and Kotlin | Proven on this PC | QuorX's own adapter on the JDK's debugger interface | the JDK runs it from source, nothing to install | JDK 21 and Gradle 8.14, 14 of 14 self-tests (PR #11) |
| GDScript (Godot) | Proven on this PC | Godot's built-in adapter | the editor runs headless with --dap-port | Godot 4.7.2 (PR #9) |
| C# | Logic tested | netcoredbg | builds the .csproj, launches the dll | no .NET SDK here: 4 tests pass, 7 skip (PR #4) |
| Go | Logic tested | Delve (dlv dap) | Delve builds the package with debug info | a stand-in adapter; no Go or Delve here (PR #7) |
| C and C++ | Logic tested | lldb-dap | cmake --build, then the target for that file | a stand-in adapter; no LLVM or compiler here (PR #7) |
| Rust | Logic tested | CodeLLDB, else lldb-dap | cargo build, then the binary for that file | program lookup on real cargo output; debugger tests skip (PR #7) |
| Dart and Flutter | Logic tested | dart debug_adapter | over stdin and stdout, through a bridge | the bridge with a stand-in adapter; no Dart here (PR #10) |
| Swift and Objective-C (macOS only) | Logic tested | lldb-dap | builds and launches, or attaches in the simulator | 8 tests on fixtures, 4 need a Mac (PR #13) |
No rows at this level.
"Logic tested" means QuorX's side ran for real against a stand-in adapter or recorded output: the launch command, the breakpoints, continue, reading variables, and Stop. It does not prove that the real adapter, at the version installed, behaves the same way. On a computer with one of those toolchains, npm run test:packs runs that pack's self-test without a key, and the Pack test report issue template takes the result.
Known limits
- MSVC builds on Windows. A program built by MSVC keeps its debug information in a
.pdbfile. LLDB reads it, so breakpoints, the stack and plain variables work, but C++ library containers and Rust'sStringandVeccan show raw fields. - A paused window can't answer. While a GUI program is stopped at a breakpoint, its window can't respond to clicks or screenshots. The desktop tool now says "paused at a breakpoint" instead of waiting 20 seconds.
- The settings label is behind. In ⚙ Settings → Safety & commands the row is still called "Debugger for Node and Python". The setting applies to every language above.
The general point
Giving an agent a tool is not free. Each tool adds to what the model has to choose between, and a powerful tool used too early can cost more steps than it saves. For a debugger, the outside evidence says "late, and for the stronger models". QuorX makes "late" the default, tells the agent what to try first, and leaves the switch with the user.