Debugging with an agent
Agents are strong at hypothesis generation and weak at admitting they do not know. Structure the session around that.
Debugging is where sessions most often spiral: symptom, guess, change, new symptom, another change, and forty minutes later the code has six speculative edits and the original bug is still there.
The structure that prevents it
Bug: [symptom]. Repro: [exact steps or failing test]. Work in this order and do not skip ahead: 1. Reproduce it and show me the actual failure output. 2. List 3 hypotheses for the cause, most likely first, with the evidence for each. Do not edit anything. 3. For the top hypothesis, tell me the cheapest way to confirm or rule it out without changing behaviour. 4. Only after we have confirmed a cause, propose a fix.
Step 1 is non-negotiable. An agent that has not reproduced the bug is pattern-matching on the symptom description, and the fix it proposes will be for a similar bug it has seen, not yours.
Step 2 exploits what models are actually good at. Generating plausible causes is a strength; picking the right one without evidence is not. Getting three ranked hypotheses out is worth more than one confident answer.
Instrument rather than guess
Step 3 is where most of the value is. Prefer cheap observations over speculative changes:
- A log line printing the actual value at the suspected point.
- A failing test that isolates the hypothesis.
- A
git log -Sorgit bisectto find when it broke. - Reading the function's callers to check an assumption about inputs.
Each of these produces evidence. A speculative fix produces a new unknown and a dirty working tree.
Keep the working tree clean
Debugging edits are throwaway. Say so, and bound them:
Add whatever logging you need to confirm this, but keep every diagnostic edit in one commit marked "wip: debug" so I can drop it. Do not change behaviour while diagnosing.
Then clear before you fix
A debugging session ends with knowledge, not with a fix. The window is now full of stack traces, ruled-out hypotheses, and diagnostic output — none of which helps write the fix, all of which competes for attention. Write down the cause in one or two lines, drop the diagnostic commit, clear, and open a fresh session:
Cause identified: parseRow() assumes the locale is set before the first row is read, but the importer sets it after reading the header. Fix: move the locale resolution into the importer constructor. Scope: src/import/*. Add a regression test that fails without the fix.
Watch out
If two hypotheses have both been ruled out and the agent proposes a third that sounds like a reach, stop. That is the signal that you have hit the limit of what is in context. Go read the code yourself, or load the part nobody has looked at. Pushing on produces increasingly creative wrong answers.
Try it
Next real bug, run the four steps strictly. Note how often the top-ranked hypothesis is right — usually high — and how often step 3 rules out something you would have spent twenty minutes "fixing".
Takeaways
- Require a reproduction before any hypothesis; otherwise it is pattern-matching on the symptom.
- Ask for ranked hypotheses with evidence, then confirm cheaply before changing anything.
- End the debugging session with the cause written down, then clear and fix in a fresh one.
Why separate diagnosing from fixing into two sessions?
Because the diagnostic session accumulates stack traces, dead hypotheses, and logging output that are irrelevant to writing the fix but compete for attention with the code that matters. The cause is one or two sentences — carry those and drop the rest.
A course by Pieter Zandbergen