Problem (an AX lesson)
Agents set breakpoints on the wrong lines because they don't have a gutter. A human points at a statement and the IDE resolves the line number; an agent reads a 1D token stream, keeps a running count (often from a snippet that started mid-file), and commits to an integer — predictably landing off-by-one-or-two on a blank line, a brace, or a comment. The result is confusing session behavior (breakpoint never hits, or hits somewhere unexpected).
Current mitigations all act after commitment or rely on the agent noticing warnings: the response echoes line content, adapters silently snap to the nearest valid line, and the schema description warns about non-executable lines. None of them move the anchor itself.
There is also a drift problem: line numbers go stale the moment the agent edits the file mid-session — which is the core fix-verify loop. restart_debugging (#238) re-applies breakpoints at their recorded lines, so after an edit the re-applied breakpoints can silently point at the wrong code.
Proposal: a progression of addressing modes
All modes return the same resolved contract — {breakpointId, line, content, verified, anchor?} — so list_breakpoints and the audit trail stay uniform. All compose with condition, logMessage, and suspendPolicy unchanged.
1. expectedContent assertion (cheap, ship first)
Keep line addressing, add optional expectedContent: string. Server hard-errors before setting anything if the trimmed line content doesn't match, and the error includes the actual content of that line plus a few neighbors. A checksum on intent: converts off-by-one from 'confusing session behavior' into an immediate, self-explanatory failure. Non-breaking, ~small diff (line content is already read for the response echo).
2. statement anchors (primary agent mode)
set_breakpoint {sessionId, file, statement: \"total = sum(prices)\", nearLine?: 80}
- Exact-match after trimming leading/trailing whitespace; anchor at the first line of a multi-line match.
- Ambiguity contract: multiple matches → error listing every match as
line: content pairs (the error message is the disambiguation UI; agent picks in one follow-up). nearLine selects the closest match when provided.
- Reject blank/comment-only anchors with a clear error.
- Re-resolution: the anchor is stored on the breakpoint record;
restart_debugging re-resolves anchors against the current file, so breakpoints survive the edit that was the whole point of the session.
- Rationale: this recruits the single most-practiced agent skill — Edit-tool
old_string matching — instead of demanding line arithmetic, a skill models are demonstrably bad at. Self-validating by construction: an anchor can't land on a blank line.
3. function breakpoints (symbol mode)
set_breakpoint {sessionId, function: \"build_cart\"} via DAP setFunctionBreakpoints, gated on supportsFunctionBreakpoints. Agents reason in symbols more reliably than in lines or statements, and names survive edits better than both.
Supporting: deliberate snapping
Where a line must be adjusted, use DAP breakpointLocations (where supported) to snap explicitly and report it loudly ('requested 12, bound to 13: total = ...') instead of relying on each adapter's silent adjustment.
Non-goals / alternatives considered
- Fuzzy content matching as the primary mode — rejected: silent wrong-match is the exact failure class we're eliminating. Fuzzy is acceptable only as an explicit
nearLine-guided fallback with a warning in the response.
- AST-based addressing — per-language parser cost across seven adapters for marginal gain over statement + function modes.
Evaluation
This is an agent-experience change, so it should be validated like one: A/B the addressing modes against line-only in a controlled harness (same tasks, same model, feature-flagged server so conditions differ only in the tool contract). Metrics: wrong-breakpoint rate against annotated intended targets, correction re-sets within a few calls, rounds/tokens per task, and root-cause-identification success. Results will be posted here; the skill (skills/debugging/) gets updated with whichever contract wins.
Rollout
expectedContent (non-breaking assertion) + loud snapping
statement/nearLine + restart re-resolution + list_breakpoints anchor display
function mode, capability-gated per adapter
Related: #236 (breakpoint management — record shape), #238 (restart re-apply — re-resolution hook), #235 (logpoints — anchors compose with logMessage).
Problem (an AX lesson)
Agents set breakpoints on the wrong lines because they don't have a gutter. A human points at a statement and the IDE resolves the line number; an agent reads a 1D token stream, keeps a running count (often from a snippet that started mid-file), and commits to an integer — predictably landing off-by-one-or-two on a blank line, a brace, or a comment. The result is confusing session behavior (breakpoint never hits, or hits somewhere unexpected).
Current mitigations all act after commitment or rely on the agent noticing warnings: the response echoes line content, adapters silently snap to the nearest valid line, and the schema description warns about non-executable lines. None of them move the anchor itself.
There is also a drift problem: line numbers go stale the moment the agent edits the file mid-session — which is the core fix-verify loop.
restart_debugging(#238) re-applies breakpoints at their recorded lines, so after an edit the re-applied breakpoints can silently point at the wrong code.Proposal: a progression of addressing modes
All modes return the same resolved contract —
{breakpointId, line, content, verified, anchor?}— solist_breakpointsand the audit trail stay uniform. All compose withcondition,logMessage, andsuspendPolicyunchanged.1.
expectedContentassertion (cheap, ship first)Keep
lineaddressing, add optionalexpectedContent: string. Server hard-errors before setting anything if the trimmed line content doesn't match, and the error includes the actual content of that line plus a few neighbors. A checksum on intent: converts off-by-one from 'confusing session behavior' into an immediate, self-explanatory failure. Non-breaking, ~small diff (line content is already read for the response echo).2.
statementanchors (primary agent mode)set_breakpoint {sessionId, file, statement: \"total = sum(prices)\", nearLine?: 80}line: contentpairs (the error message is the disambiguation UI; agent picks in one follow-up).nearLineselects the closest match when provided.restart_debuggingre-resolves anchors against the current file, so breakpoints survive the edit that was the whole point of the session.old_stringmatching — instead of demanding line arithmetic, a skill models are demonstrably bad at. Self-validating by construction: an anchor can't land on a blank line.3.
functionbreakpoints (symbol mode)set_breakpoint {sessionId, function: \"build_cart\"}via DAPsetFunctionBreakpoints, gated onsupportsFunctionBreakpoints. Agents reason in symbols more reliably than in lines or statements, and names survive edits better than both.Supporting: deliberate snapping
Where a line must be adjusted, use DAP
breakpointLocations(where supported) to snap explicitly and report it loudly ('requested 12, bound to 13:total = ...') instead of relying on each adapter's silent adjustment.Non-goals / alternatives considered
nearLine-guided fallback with a warning in the response.Evaluation
This is an agent-experience change, so it should be validated like one: A/B the addressing modes against line-only in a controlled harness (same tasks, same model, feature-flagged server so conditions differ only in the tool contract). Metrics: wrong-breakpoint rate against annotated intended targets, correction re-sets within a few calls, rounds/tokens per task, and root-cause-identification success. Results will be posted here; the skill (skills/debugging/) gets updated with whichever contract wins.
Rollout
expectedContent(non-breaking assertion) + loud snappingstatement/nearLine+ restart re-resolution +list_breakpointsanchor displayfunctionmode, capability-gated per adapterRelated: #236 (breakpoint management — record shape), #238 (restart re-apply — re-resolution hook), #235 (logpoints — anchors compose with logMessage).