| name | godebug-verify |
| description | Proactive runtime verification for AI-generated Go code. Use when you need to CONFIRM code correctness before marking tasks done - closing the loop from generation to runtime proof. Complements godebug (reactive debugging) with verification mindset. |
godebug-verify - Runtime Verification for AI Agents
Proactive verification skill that teaches AI agents to confirm their own code works at runtime. This is "closing the loop" - never mark code done until you've verified it executes correctly.
"How to be effective with coding agent is always like you have to close the loop. It needs to be able to debug and test itself."
1. Verification vs Debugging
When to Use Which Skill
| Situation | Use | Skill |
|---|
| Code you just wrote | Verify it works | godebug-verify |
| Existing bug report | Find the cause | godebug |
| "Is this correct?" | Confirm behavior | godebug-verify |
| "What's broken?" | Investigate failure | godebug |
| Before marking done | Runtime proof | godebug-verify |
| After test failure | Debug the failure | godebug |
| Concurrency concerns | Verify synchronization | godebug-verify |
| Crash or panic | Find root cause | godebug |
The Mindset Difference
Debugging (godebug):
- Reactive: Something broke, find why
- Question: "What went wrong?"
- Goal: Fix the bug
Verification (godebug-verify):
- Proactive: Code looks right, prove it runs right
- Question: "Does this actually work?"
- Goal: Confirm before shipping
The "Closing the Loop" Principle
[Write Code] → [Looks Correct] → [VERIFY AT RUNTIME] → [Mark Done]
↓ ↓
Static analysis Actually executes?
Code review Values are correct?
"Seems right" Timing is right?
Never mark code as complete based only on:
- "The logic looks correct"
- "Tests pass" (tests might not cover this path)
- "It compiles"
Always verify when:
- You wrote concurrent code (goroutines, channels, sync primitives)
- You changed control flow logic
- You modified data structures
- The fix was non-obvious
2. The Verification Protocol
HAEC: Hypothesis → Assertion → Execution → Conclusion
Every verification follows this four-step protocol:
HYPOTHESIS → State what MUST be true if code is correct
ASSERTION → Define observable evidence (breakpoints, values)
EXECUTION → Run to verification points
CONCLUSION → CONFIRMED or REFUTED based on evidence
Protocol in Practice
Note on file paths: Breakpoint file paths can be specified as:
- Short filename (e.g.,
main.go:42) - works if the file is unique in the debug session
- Relative path (e.g.,
pkg/myapp/main.go:42) - use when multiple files share the same name
- Absolute path - always unambiguous
If a breakpoint fails with "could not find file", run godebug sources to see how files are listed, then use that path.
ADDR=localhost:4445
godebug --addr $ADDR break pkg/myapp/main.go:42
godebug --addr $ADDR break pkg/myapp/main.go:44
godebug --addr $ADDR continue
Conclusion States
| State | Meaning | Action |
|---|
| CONFIRMED | Evidence matches hypothesis | Proceed with confidence |
| REFUTED | Evidence contradicts hypothesis | Diagnose → Fix → Re-verify |
| INCONCLUSIVE | Can't determine from evidence | Adjust breakpoints, re-run |
3. Verification Scenarios
3.1 Function Return Values
Hypothesis: Function returns expected value for given input.
godebug --addr $ADDR break "main.calculateTotal"
godebug --addr $ADDR continue
godebug --addr $ADDR next
godebug --addr $ADDR next
godebug --addr $ADDR eval "total"
3.2 WaitGroup Synchronization
Hypothesis: All wg.Add() calls complete before wg.Wait() returns.
godebug --addr $ADDR break "sync.(*WaitGroup).Add"
godebug --addr $ADDR break "sync.(*WaitGroup).Wait"
godebug --addr $ADDR continue
godebug --addr $ADDR stack
godebug --addr $ADDR continue
Real Example (buggy code):
for i := 0; i < 10; i++ {
go func(id int) {
wg.Add(1)
defer wg.Done()
}(i)
}
wg.Wait()
godebug --addr $ADDR break main.go:18
godebug --addr $ADDR break main.go:27
godebug --addr $ADDR continue
3.3 Mutex Protection
Hypothesis: Shared variable is always accessed while holding mutex.
godebug --addr $ADDR break "sync.(*Mutex).Lock"
godebug --addr $ADDR break main.go:25
godebug --addr $ADDR continue
godebug --addr $ADDR stack
Real Example (buggy code):
for i := 0; i < 1000; i++ {
go func() {
counter++
}()
}
3.4 Channel Communication
Hypothesis: Channel receive gets the expected value from sender.
godebug --addr $ADDR break "runtime.chansend1"
godebug --addr $ADDR break "runtime.chanrecv1"
godebug --addr $ADDR break main.go:30
godebug --addr $ADDR break main.go:45
godebug --addr $ADDR continue
godebug --addr $ADDR eval "value"
godebug --addr $ADDR continue
godebug --addr $ADDR eval "result"
3.5 Deadlock Prevention
Hypothesis: Lock acquisition order is consistent (no circular wait).
godebug --addr $ADDR break "sync.(*Mutex).Lock"
godebug --addr $ADDR continue
godebug --addr $ADDR stack
godebug --addr $ADDR goroutines
Real Example:
func transferAtoB() {
resourceA.Lock()
resourceB.Lock()
}
func transferBtoA() {
resourceB.Lock()
resourceA.Lock()
}
3.6 Control Flow Verification
Hypothesis: Code takes the expected branch.
godebug --addr $ADDR break main.go:50
godebug --addr $ADDR continue
godebug --addr $ADDR eval "err"
godebug --addr $ADDR next
godebug --addr $ADDR list
4. The Self-Correction Loop
When verification is REFUTED, don't just fix blindly. Follow this loop:
REFUTED
↓
DIAGNOSE: Why did reality differ from expectation?
↓
FIX: Make minimal change to correct the issue
↓
RE-VERIFY: Run the SAME verification again
↓
CONFIRMED? → Done
↓ No
Back to DIAGNOSE
Self-Correction Example
godebug --addr $ADDR stack
godebug --addr $ADDR restart
godebug --addr $ADDR break "sync.(*WaitGroup).Add"
godebug --addr $ADDR break "sync.(*WaitGroup).Wait"
godebug --addr $ADDR continue
godebug --addr $ADDR stack
Never Mark Done Until CONFIRMED
[Code Written] → [REFUTED] → [Fix] → [Still REFUTED] → [Fix] → [CONFIRMED] → [Mark Done]
↑ ↑
└───────────────────────┘
Keep iterating until CONFIRMED
5. Token-Efficient Verification Patterns
Verification should be surgical. Don't dump all state; query exactly what you need.
Efficient Queries
| Need | Inefficient | Efficient |
|---|
| One field | locals (~200 tokens) | eval "user.ID" (~50 tokens) |
| Slice length | locals | eval "len(items)" |
| Map key | locals | eval "cache[\"key\"]" |
| Condition | locals + mental eval | eval "count > threshold" |
| Pointer nil check | locals | eval "ptr == nil" |
Targeted Verification Commands
godebug --addr $ADDR locals
godebug --addr $ADDR args
godebug --addr $ADDR stack
godebug --addr $ADDR eval "wg"
godebug --addr $ADDR eval "len(results)"
godebug --addr $ADDR eval "err != nil"
Minimal Breakpoint Strategy
godebug --addr $ADDR break main.go:10
godebug --addr $ADDR break main.go:15
godebug --addr $ADDR break main.go:20
godebug --addr $ADDR break main.go:25
godebug --addr $ADDR break main.go:25
6. Workflow Examples
Example 1: Verify Concurrent Counter
You wrote code to increment a counter from multiple goroutines.
dlv debug ./testdata/concurrency_bugs/race_counter --headless \
--api-version=2 --listen=:4445 --accept-multiclient &
sleep 2
ADDR=localhost:4445
godebug --addr $ADDR break main.go:22
godebug --addr $ADDR continue
godebug --addr $ADDR eval "counter"
Example 2: Verify Error Propagation
You wrote error handling code.
godebug --addr $ADDR break inner.go:15
godebug --addr $ADDR break caller.go:30
godebug --addr $ADDR continue
godebug --addr $ADDR eval "err"
godebug --addr $ADDR continue
godebug --addr $ADDR eval "err"
Example 3: Verify Mutex-Protected Critical Section
dlv debug ./concurrent-app --headless \
--api-version=2 --listen=:4445 --accept-multiclient &
sleep 2
ADDR=localhost:4445
godebug --addr $ADDR break main.go:50
godebug --addr $ADDR continue
godebug --addr $ADDR goroutines
godebug --addr $ADDR goroutine 1
godebug --addr $ADDR list
godebug --addr $ADDR goroutine 2
godebug --addr $ADDR list
Example 4: Verify Channel Doesn't Block Forever
godebug --addr $ADDR break main.go:60
godebug --addr $ADDR break main.go:61
godebug --addr $ADDR continue
7. Integration with godebug Skill
This skill focuses on the verification mindset. For command syntax and detailed options, see the godebug skill.
Command Reference (in godebug skill)
| Command | Use in Verification |
|---|
break | Set assertion points |
continue | Run to next assertion |
eval | Check specific values |
goroutines | Verify concurrent state |
stack | Verify call path |
list | Confirm current location |
When to Hand Off to godebug
Verification discovers a bug → switch to debugging:
| Verification Result | Next Step |
|---|
| CONFIRMED | Done - proceed with confidence |
| REFUTED (simple) | Fix inline, re-verify |
| REFUTED (complex) | Switch to godebug for deep investigation |
| INCONCLUSIVE | Adjust assertions, re-verify |
Handoff Trigger
[Verification REFUTED]
↓
Is root cause obvious?
↓
YES → Fix → Re-verify
NO → Use godebug skill for deep debugging
8. Result Interpretation
CONFIRMED - Proceed with Confidence
{"value": "100"}
{"breakpoint": {"id": 1, "line": 42}}
Action: Code verified. Mark task complete.
REFUTED - Capture State, Fix, Re-verify
{"value": "97"}
Action:
- Capture the actual state (save the JSON output)
- Identify the discrepancy
- Fix the code
- Re-run exact same verification
- Repeat until CONFIRMED
INCONCLUSIVE - Adjust and Retry
// Breakpoint never hit - wrong location?
// Program exited before reaching assertion point
// Value is correct but for wrong reason
Action:
- Check breakpoint locations
- Verify code path actually executes
- Add more assertion points
- Re-run with adjusted strategy
9. Detection Criteria
Use this skill when:
- You just wrote Go code and want to verify it works
- You're working with goroutines, channels, or sync primitives
- You need to prove concurrent code is correctly synchronized
- Before marking a coding task as complete
- User asks to "verify", "confirm", or "prove" code behavior
- You want to "close the loop" on generated code
Do NOT use this skill when:
- You're investigating an existing bug (use godebug)
- You need to find why something crashed (use godebug)
- You just need to run tests (use
go test)
- The code is trivial and obviously correct
10. Quick Reference Card
┌─────────────────────────────────────────────────────────────┐
│ VERIFICATION PROTOCOL │
├─────────────────────────────────────────────────────────────┤
│ HYPOTHESIS: State what MUST be true │
│ ASSERTION: Set breakpoints at verification points │
│ EXECUTION: godebug continue → eval specific values │
│ CONCLUSION: CONFIRMED → done | REFUTED → fix → re-verify │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ COMMON ASSERTIONS │
├─────────────────────────────────────────────────────────────┤
│ WaitGroup: break "sync.(*WaitGroup).Add" │
│ break "sync.(*WaitGroup).Wait" │
│ │
│ Mutex: break "sync.(*Mutex).Lock" │
│ │
│ Value check: eval "variable == expected" │
│ │
│ Control flow: break at branch, then list after next │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ TOKEN-EFFICIENT │
├─────────────────────────────────────────────────────────────┤
│ DO: eval "x.Field" (targeted) │
│ NOT: locals (dumps everything) │
│ │
│ DO: Single assertion point │
│ NOT: Breakpoints everywhere │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ SELF-CORRECTION │
├─────────────────────────────────────────────────────────────┤
│ REFUTED → Diagnose → Fix → Re-verify → CONFIRMED? │
│ ↑ │ No │
│ └──────────────────────────────┘ │
│ │
│ Never mark done until CONFIRMED │
└─────────────────────────────────────────────────────────────┘