Debugging in depth
The full DAP guide: adapters, breakpoints, and driving a session.
Luxe drives a real debugger over the Debug Adapter Protocol (DAP) through the debug tool,
so the agent can set breakpoints, step, and inspect/modify live state instead of print-debugging.
The tool is first-class and always in the model’s toolset; its description steers the model to
prefer it over print-debugging, and Luxe reinforces that at runtime (see
Closing the loop).
Adapters
Built-in (install the binary; Luxe knows how to launch + route it):
| Language | Adapter | Files |
|---|---|---|
| Python | debugpy | .py |
| Rust / C / C++ | CodeLLDB (or lldb-dap / gdb) | compiled binaries |
| Go | Delve | .go |
Add more without a code change in ~/.config/luxe/debug-adapters.json (a JSON array), reusing a
built-in launch dialect and a transport:
[
{
"id": "ruby",
"command": ["rdbg", "--open", "--port", "${DAP_ADDR}"],
"extensions": ["rb"],
"dialect": "debugpy",
"transport": "tcp_dial_in"
}
]
dialect— the launch-arg shape:debugpy|lldb|gdb|delve.transport—stdio(default) ortcp_dial_in(the adapter dials back into a loopback port Luxe binds;${DAP_ADDR}is replaced withhost:port).extensionsauto-routes matching files; any adapter is also selectable via the tool’sadapterargument.
Run /debug in the TUI to see configured adapters, which are installed, and any live sessions.
Workflow
launch (or attach) returns a session id; every follow-up call passes it. On each stop the
result already includes the top frame + locals, so a breakpoint hit is immediately actionable.
- Start:
launch{program, args?, cwd?, breakpoints?, stopOnEntry?}orattach{pid | port}. - Breakpoints:
set_breakpoints{session, path, lines, condition?},set_function_breakpoints{session, functions}. - Run:
continue | next | step_in | step_out | pause{session, threadId?}. - Inspect:
threads | stack_trace{session},scopes{session, frameId},variables{session, variablesReference},evaluate{session, expression, frameId?}. - Mutate / end:
set_variable{session, variablesReference, name, value},output | sessions | terminate{session}.
A worked example (breakpoint → inspect → fix)
// 1. Launch, breaking where the wrong value appears.
debug({ "action": "launch", "program": "target/debug/app",
"breakpoints": [{ "path": "src/parse.rs", "lines": [42] }] })
// → stops at parse.rs:42, result shows the top frame + locals
// 2. Inspect the suspicious expression at the current frame.
debug({ "action": "evaluate", "session": "<id>", "expression": "tokens.len()" })
// 3. Step and re-inspect until the cause is clear, then edit the source and re-run.
debug({ "action": "next", "session": "<id>" })
Remote / already-running processes
Attach to a running process by pid (native) or to a listening adapter by port:
debug({ "action": "attach", "pid": 12345 }) // native (lldb/gdb)
debug({ "action": "attach", "port": 5678 }) // e.g. debugpy --listen 5678
Closing the loop
The debugger is wired into Luxe’s feedback loop so the model reaches for it at the right time:
- Diagnostics on edit — after every
edit/write, Luxe surfaces fresh LSP errors/warnings in the tool result (see Language intelligence), so many bugs are caught before they need a debugger. - Failure nudge — after 3 consecutive failing test runs, Luxe appends a one-time hint
suggesting a breakpoint-and-inspect pass rather than more edit-and-retry
(toggle
LUXE_DEBUG_NUDGE=0). - DEBUG dock section — while a session is live, the Context Dock shows a DEBUG section with each session’s program and whether it’s running or stopped (with the stop reason). It’s hidden when nothing is being debugged.
Notes
- Adapters are external binaries: a missing one degrades gracefully with an install hint; the agent
can install it (via
bash) and Luxe launches it on the next call. - Sessions are reused across tool calls and reaped on quit / project switch (bounded shutdown, then kill-on-drop), so a stuck adapter can’t linger.
- Program output is captured byte-for-byte as the adapter sends it, so a partial line
(
print(..., end=""), a\rprogress bar) reads the way it would in a terminal. Output events don’t align with line boundaries, so Luxe only records where the stream changed (stdout → stderr → adapter console) and breaks the display there — otherwise two unrelated messages glue into one nonsense token mid-line.
Capturing a TUI rendering fault
If the interface itself ever garbles (stray escape sequences, a half-drawn frame), the useful evidence is the byte stream, not a screenshot. Reproduce under a recorder:
script -f /tmp/luxe.tty -c luxe # raw byte log, replay with `cat`/`scriptreplay`
asciinema rec /tmp/luxe.cast -c luxe # if you prefer a shareable cast
RUST_LOG=luxe_tui=debug,luxe_core=debug luxe # logs land in .luxe/logs/luxe.log
script keeps every byte Luxe wrote, which is what identifies the culprit — a debug adapter or
subprocess writing to the terminal directly, a mis-sized frame, or an escape sequence Luxe emitted
itself. Attach the log (and .luxe/logs/luxe.log for the same window) to the report.