Debug Adapter Protocol (DAP)
Debug Adapter Protocol (DAP)
A wire format. JSON over stdio (or TCP). Defines messages a generic debugger frontend sends to a generic debugger backend.
Designed by Microsoft. Same idea as LSP but for debuggers instead of language servers. The point: an editor doesn't have to integrate with each debugger separately. It speaks DAP; the debugger ships an adapter that speaks DAP.
What DAP is
- A protocol, not an implementation. Just message shapes and semantics.
- Bidirectional JSON framed by
Content-Length: N\r\n\r\nheaders (same shape as LSP). - ~50 message types:
initialize,launch,setBreakpoints,continue,stepIn,stackTrace,scopes,variables,evaluate,disconnect, ... - Push-based — the debugger emits events (
stopped,output,terminated) at unpredictable times.
What DAP is NOT
- Not a debugger. It's the interface to one. The actual debugging happens in the DAP Adapter and below it (LLDB, GDB, ptrace, etc.).
- Not a runtime. No code executes inside DAP — it's just messages on a wire.
- Not designed for shell scripts or AI agents. Designed for IDE plugins. Bridging it to other consumers (what Lazydap does) is real work.
Layer position
Frontend (lazydap, VS Code) ── DAP ──> Adapter (codelldb, debugpy) ── ... ──> Debuggee
DAP sits between the frontend and the adapter. Above it: UI concerns. Below it: real debugging via DAP Adapter → real debugger → ptrace / runtime hooks.
Concrete example
// Frontend → adapter
{ "seq": 1, "type": "request", "command": "setBreakpoints",
"arguments": { "source": {"path": "main.c"}, "breakpoints": [{"line": 42}] } }
// Adapter → frontend
{ "seq": 2, "type": "response", "request_seq": 1, "command": "setBreakpoints",
"success": true, "body": { "breakpoints": [{"verified": true, "line": 42, "id": 1}] } }
// Adapter → frontend (asynchronous event)
{ "seq": 3, "type": "event", "event": "stopped",
"body": { "threadId": 1, "reason": "breakpoint", "hitBreakpointIds": [1] } }
That's three messages. The first two are request/response (correlated by request_seq). The third is an event — push, no client request triggered it.
Why DAP exists
Pre-DAP: every editor wrote custom integration with every debugger. VS Code with GDB, VS Code with LLDB, Vim with GDB, Vim with LLDB, etc — N×M problem.
Post-DAP: write one DAP client (your editor) and one DAP adapter per debugger. N+M problem. Same architectural insight as LSP.
DAP's blind spot
DAP assumes one IDE-style client per session. Doesn't natively support multi-client subscription, agent-friendly synchronous wrappers, or shell-style scripting. That's the gap Lazydap fills with its own protocol layered on top.
See also
- DAP Adapter — what speaks DAP on the debugger side
- How Debuggers Actually Work — full layer cake
- Lazydap — the project that wraps DAP for shells/agents
- DAP spec: https://microsoft.github.io/debug-adapter-protocol/specification.html