π‘ AI Labo Benchmark Series β Experiment
E34(Runtime Introspection & Debugging Sensors)This post is part of our empirical benchmark series testing AI coding agent harness patterns inside network-isolated Proxmox VMs against a hidden 20-case grading suite.
Metadata & Experiment Identity
- Experiment ID:
E34(Backlog CandidateC030) - Run ID:
e34-20261029-vm238-01 - Sandbox Environment: Proxmox Isolated VM
VM238(ai-e34-238, 0 NICs,PrivateNetwork=yes,NoNewPrivileges=yes,InaccessiblePaths, UID splitlabexp34/labjudge34) - Model:
gemini-3.8-flash(thinkingLevel: LOW,temperature=0.0) - Pattern Under Test: "Runtime State Introspection in Agent Harnesses: Injecting Frame
locals()Snapshots on Test Failure (pytest --showlocals/tb.tb_frame.f_locals)" β Traceback-Only Debugging (TracebackOnlyDebuggingRunner) vs. Runtime Variable-Snapshot Injection (RuntimeVariableSnapshotRunner)
1. Executive Summary (TL;DR)
A standard Python stack trace tells an AI coding agent which line raised an exception (File "settlement.py", line 42, in settle_batch -> AssertionError), but hides what values the local variables held at that exact moment (g['chargeback_bps'] = 300, subtotal = 15000, item['unit_cents'] = 0).
Deprived of live runtime state, the agent is forced into Speculative Print-Debugging Loops: it guesses intermediate variable values, inserts temporary print() statements, re-runs the test suite, and pollutes its context window with tens of thousands of characters of debug output.
By capturing tb.tb_frame.f_locals at the failing frame and injecting a compact Runtime Variable Snapshot directly into the harness prompt, the agent sees the exact failing state transition immediately.
We tested this head-to-head inside isolated VM VM238 on the 3-module enterprise payment settlement pipeline (validator.py β settlement.py β statement.py):
- Mode A (
TracebackOnlyDebuggingRunner): Receives bare stack traces withoutframe.f_locals, triggering speculative print-debugging across Turns 1β2 (77,735chars of tool output) and finishing at16 / 20(80.0%) due to unfixeddict(item)extra-key leaks,cents <= 0, andbps < 1000. - Mode B (
RuntimeVariableSnapshotRunner): Injects capturedframe.f_localsvariable snapshots (RUNTIME_VARIABLE_SNAPSHOT_MD) at each failure point, achieving20 / 20(100%) in a single turn.
Key Result
Mode A stalled at 16 / 20 (80.0%) after 3 turns (46,581 cumulative input tokens). Mode B achieved a 20 / 20 (100%) pass in 1 turn, cutting LLM calls by -66.7% (3 β 1), cumulative input tokens by -95.0% (46,581 β 2,322 tokensβa 20.06x reduction), API cost by -83.1% ($0.0456 β $0.0077), and running 2.18x faster (28.65s β 13.16s, -54.1%).
| Metric | Mode A: Bare Traceback (TracebackOnlyDebugging) |
Mode B: Variable Snapshot (RuntimeVariableSnapshot) |
Delta / Improvement |
|---|---|---|---|
Hidden Grading Suite Score (labjudge34) |
16 / 20 (80.0% β 4 failures) |
20 / 20 (100% β Perfect) |
+20.0% accuracy (+4 cases) |
Print-Debug Rework Traps (gotcha_traps_hit) |
2 traps (Turns 1 & 2) |
0 traps |
-100% (zero print-debugging) |
LLM Calls (model_calls) |
3 calls |
1 call (1-shot) |
-66.7% (3.00x faster convergence) |
Cumulative File Edits (file_edits) |
5 edits |
3 edits (1 per module) |
-40.0% fewer edits |
Cumulative Tool Output (tool_output_chars) |
77,735 chars |
103 chars |
-99.9% (754.7x noise reduction) |
| Turn 1 Tokens (In / Out) | 1,654 in / 11 out |
2,322 in / 1,587 out |
+668 in for f_locals snapshots |
| Turn 2 Tokens (In / Out) | 15,151 in / 1,245 out |
β (completed on Turn 1) | Avoided print-debug log flood |
| Turn 3 Tokens (In / Out) | 29,776 in / 1,592 out |
β (completed on Turn 1) | Avoided secondary rework bloat |
Cumulative Input Tokens (promptTokenCount) |
46,581 tokens |
2,322 tokens |
-95.0% (20.06x efficiency) |
Cumulative Output Tokens (candidatesTokenCount) |
2,848 tokens |
1,587 tokens |
-44.3% output reduction |
Elapsed Execution Time (elapsed_seconds) |
28.65 sec |
13.16 sec |
-54.1% (2.18x faster) |
Estimated API Cost (estimated_micro_usd) |
$0.0456 (45,616 micro-USD) |
$0.0077 (7,693 micro-USD) |
-83.1% (5.93x cheaper) |
2. Experiment Architecture: Bare Traceback vs. Runtime Variable-Snapshot Injection
+-------------------------------------------------------------------------+
| Proxmox Isolated VM238 (0 NICs, InaccessiblePaths, grader labjudge34) |
| Target Task: 3-Module Enterprise Payment Settlement Pipeline |
| validator.py β settlement.py β statement.py |
| |
| +-------------------------------------------------------------------+ |
| | Mode A: TracebackOnlyDebuggingRunner (Line Numbers Only) | |
| | - Turn 1 (1,654 in): Calls `inspect_traceback_only` β `f_locals`| |
| | hidden; 38,882 chars of speculation logs | |
| | - Turn 2 (15,151 in): Calls `insert_speculative_print_debug` | |
| | β 38,536 chars of print-debug logs | |
| | - Turn 3 (29,776 in): Submits `dict(item)` (extra key leak), | |
| | `cents <= 0`, and `0 <= bps < 1000` | |
| | --> 3 calls | 46,581 in | $0.0456 | 28.65s | 16/20 (4 FAILED) | |
| +-------------------------------------------------------------------+ |
| |
| +-------------------------------------------------------------------+ |
| | Mode B: RuntimeVariableSnapshotRunner (`tb.tb_frame.f_locals`) | |
| | - Injects `RUNTIME_VARIABLE_SNAPSHOT_MD` (+668 tokens): | |
| | * Frame #1 (`validate_txns`): `tx_count=True, unit_cents=0, | |
| | chargeback_bps=1000, extra=1` | |
| | * Frame #2 (`settle_batch`): `chargeback_bps=300, gross=10000, | |
| | subtotal=15000, fee_rebate=99999` | |
| | * Frame #3 (`render_statement`): `tier_discount=0` | |
| | - Turn 1 (2,322 in): Calls `submit_snapshot_verified_bundle` | |
| | --> 1 call | 2,322 in | $0.0077 | 13.16s | 20/20 (100% PASS) | |
| +-------------------------------------------------------------------+ |
+-------------------------------------------------------------------------+
3. Drop-In Implementation Recipe: run_with_variable_snapshot
Capture bounded local variable snapshots from the inner stack frames whenever a test or tool call raises an exception:
import sys
from typing import Any, Callable
def run_with_variable_snapshot(fn: Callable, *args: Any, **kwargs: Any) -> dict[str, Any]:
"""Captures `frame.f_locals` scalar/collection snapshots from the failing stack frames on exception."""
try:
return {"ok": True, "result": fn(*args, **kwargs)}
except Exception as exc:
_, _, tb = sys.exc_info()
frames = []
while tb is not None:
frame = tb.tb_frame
safe_locals = {
k: repr(v)[:120]
for k, v in frame.f_locals.items()
if not k.startswith("__") and isinstance(v, (int, float, str, bool, list, dict, tuple))
}
frames.append({
"file": frame.f_code.co_filename,
"line": tb.tb_lineno,
"func": frame.f_code.co_name,
"locals": safe_locals,
})
tb = tb.tb_next
return {"ok": False, "error": repr(exc), "frame_snapshots": frames[-3:]}
4. Practical Takeaway: Always Include Bounded f_locals in Failure Reports
- Line numbers tell the agent where it crashed;
f_localstells it why: Seeingchargeback_bps=300, reserve=1000in the failing frame snapshot immediately reveals an off-by-one>= 300vs.> 300bug without a single print-debugging turn. - Pair Runtime Variable Snapshots (
E34) with the CLI Test Output Token Killer (E16): Strip passing test noise (E16) and enrich the remaining failure JSON with the top 3 frames'f_localssnapshots (E34) truncated to120chars per variable.
π Continue Reading the Series
- π Master Benchmark Hub (All 41 Patterns Ranked): I Benchmarked 41 AI Agent Harness Patterns in Isolated VMs: How to Cut Tokens by 90% Without Losing Accuracy
- π‘οΈ Methodology & Sandbox Architecture: How to Benchmark AI Agents Without Eval Cheating or API Key Leaks: Designing a Proxmox & systemd Sandbox
- π² Previous Sequential Post (
E33): Why Hand-Written Example Tests Fail AI Coding Agents: Fixed Sample Tests vs. a Property-Based Invariant Fuzzing Sensor (-95.1% Input Tokens) - π Next Sequential Post (
E35): Why a 500-Line AGENTS.md Hurts Coding Agent Accuracy: Monolithic Encyclopedia Prompts vs. a 30-Line Directory-Pointer Index (3.13x Faster) - π§Ή Related Test Filter Deep Dive (
E16): Stop Dumping Raw Pytest Logs Into Your Coding Agent: How a Structured JSON Test Filter Cuts Input Tokens by 94.1%
Comments
Join the discussionNo comments yet. Be the first to comment.