cd /news/ai-agents/stop-dumping-raw-pytest-logs-into-yo… · home › topics › ai-agents › article
[ARTICLE · art-161788] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Stop Dumping Raw Pytest Logs Into Your Coding Agent: How a Structured JSON Test Filter Cuts Input Tokens by 94.1%

An AI Labo benchmark experiment (E16) found that filtering raw CLI test-runner output into a structured JSON summary before feeding it to a coding agent cut cumulative input tokens by 94.1% (92,385 to 5,445) and API cost by 87.0% ($0.0770 to $0.0100). Running inside an isolated Proxmox VM against a hidden 20-case grading suite, the JSON filter harness compressed 117,391 characters of pytest output to 1,604 characters (-98.6%), fixed all three payment-pipeline modules in two LLM calls instead of three, and finished 35% faster while both modes scored 20/20.

by read6 min views1 publishedNov 1, 2026

💡 AI Labo Benchmark Series — Experiment E16 (Ranked #3 out of 41 Patterns)

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.

E16 (Backlog Candidate C012) e16-20261028-vm220-01 VM220 (ai-e16-220, 0 NICs, virtio-serial QGA relay, systemd UID separation)gemini-3.8-flash (thinkingLevel: LOW) rtk-ai/rtk, Rust Token Killer) & SWE-agent Output Compression — What is the single biggest token sink in an autonomous coding agent loop? Surprisingly, it isn't the source code itself—it is unfiltered CLI test runner output.

When an agent executes pytest -vv --tb=long, cargo test, or npm test on a realistic repository where 76 tests pass and 4 fail, dumping the raw terminal output (ANSI color codes, environment banners, 76 [PASS] lines, captured DEBUG stdout, and multi-frame internal tracebacks) directly into chat history adds 60,000+ characters (28,000+ tokens) per test run.

To measure the impact of deterministic CLI output filtering, we benchmarked a 3-module payment settlement pipeline (validator.py, settlement.py, statement.py) across two harness modes inside isolated VM VM220:

RawTerminalOutputRunner): pytest terminal stdout (60,749 chars on Turn 1; 117,391 chars cumulative) directly to the conversation history.TokenKillerFilterRunner): 1,506-character structured JSON summary of the 4 failed assertions Compressing raw CLI test output from 117,391 chars ➔ 1,604 chars (-98.6% reduction, 73.2x compression) enabled Mode B to fix all three modules in a single edit turn (2 LLM calls total vs. 3 in Mode A). Mode B cut cumulative input tokens by -94.1% (92,385 ➔ 5,445 tokens, 16.97x efficiency), API cost by -87.0% ($0.0770 ➔ $0.0100), and wall-clock time by -35.0% (1.54x faster) while achieving 20 / 20 (100%) on the hidden grading suite.

Metric Mode A: Raw Terminal Output ( RawTerminalOutput ) Mode B: Semantic JSON Filter ( TokenKillerFilter ) Delta / Improvement
Hidden Grading Suite Score (20 Cases) 20 / 20 (100%) 20 / 20 (100%) Both passed all 20 cases
Cumulative CLI Tool Output (tool_output_chars) 117,391 chars 1,604 chars -98.6% (73.19x compression)
First Test Run Output Size 60,749 chars 1,506 chars -97.5% (76 PASS lines & ANSI stripped)
LLM Calls (model_calls) 3 calls 2 calls (1 test run + 1 full fix) -33.3% (No intermediate partial patch)
Turn 1 Input / Output Tokens 2,406 in /10 out 2,415 in / 10 out Identical initial test invocation
Turn 2 Input / Output Tokens 31,170 in /484 out 3,030 in / 1,561 out Turn 2 input cut by -90.3%
Turn 3 Input / Output Tokens 58,809 in /1,574 out — (Completed on Turn 2) Avoided 58,809-token log snowball
Cumulative Input Tokens (promptTokenCount) 92,385 tokens 5,445 tokens -94.1% (16.97x token efficiency)
Cumulative Output Tokens (candidatesTokenCount) 2,068 tokens 1,571 tokens -24.0% reduction
Elapsed Execution Time (elapsed_seconds) 25.94 sec 16.88 sec -35.0% (1.54x faster)
Estimated API Cost (estimated_micro_usd) $0.0770 (77,045 micro-USD ) $0.0100 (9,976 micro-USD) -87.0% (7.72x cheaper)
+-------------------------------------------------------------------------+
| Proxmox Isolated VM220 (0 NICs, virtio-serial QGA, systemd UID split)   |
|  Target Suite: 80-Test Suite (76 PASS / 4 FAIL across 3 modules)        |
|                                                                         |
|  +-------------------------------------------------------------------+  |
|  | Mode A: RawTerminalOutputRunner (Unfiltered pytest -vv --tb=long) |  |
|  |  - Turn 1: run_raw_pytest     --> Returns 60,749 chars raw log    |  |
|  |  - Turn 2: stage_partial_fix  --> Input jumps to 31,170 tokens!   |  |
|  |                                   Returns 56,544 chars raw log    |  |
|  |  - Turn 3: submit_settlement  --> Input hits 58,809 tokens!       |  |
|  |                                   (Total: 92,385 input tokens)    |  |
|  +-------------------------------------------------------------------+  |
|                                                                         |
|  +-------------------------------------------------------------------+  |
|  | Mode B: TokenKillerFilterRunner (RTK-Style Semantic JSON Filter)  |  |
|  |  - Turn 1: run_filtered_tests --> Strips 76 PASS & ANSI noise;    |  |
|  |                                   returns 1,506-char failure JSON |  |
|  |  - Turn 2: submit_settlement  --> Input is only 3,030 tokens!     |  |
|  |                                   Fixes all 3 files in 1 shot     |  |
|  |                                   (Total: 5,445 input tokens)     |  |
|  +-------------------------------------------------------------------+  |
+-------------------------------------------------------------------------+

RawTerminalOutput): How 2 Test Runs Burned 92,385 Input Tokens Tracking execution-traces.json and usage.json reveals how fast raw terminal output compounds in an agent's message array:

2,406 in / 10 out):{"tool": "run_raw_pytest"}. The runner executes the 80-test suite and returns 60,749 characters``[PASS] lines with AUDIT_TRACE dumps, and long multi-frame tracebacks for the 4 failures.31,170 in / 484 out):+28,764 tokens`` 2,406 ➔ 31,170). Distracted by the 60KB wall of text, the agent only patches validator.py (stage_partial_fix), triggering a second test run that appends another 56,544 characters`` 58,809 in / 1,574 out): 58,809 input tokens``92,385 cumulative input tokens).TokenKillerFilter): 1,506 Characters of Pure Signal When Mode B called {"tool": "run_filtered_tests"} on Turn 1, the harness preprocessor stripped all 76 passing tests, ANSI escape sequences, and DEBUG noise—returning a 1,506-character (615-token) JSON payload containing only the 4 failing tests ( test, file, line, assertion, expected_vs_actual):

{
  "summary": {"total": 80, "passed": 76, "failed": 4, "filtered_noise_chars_removed": 59243},
  "failures": [
    {
      "test": "test_validator_contract",
      "file": "validator.py",
      "line": 14,
      "assertion": "ALLOWED_CURRENCIES must include 'JPY'; reject empty list & bool; allow unit_cents==0 & chargeback_bps==1000; do not mutate input dict."
    },
    {
      "test": "test_settlement_ordering_and_reserve",
      "file": "settlement.py",
      "line": 22,
      "assertion": "Preserve first-seen (merchant_id, currency) order (never sorted(order)); apply 10% reserve strictly when chargeback_bps > 300."
    },
    {
      "test": "test_settlement_discounts_and_rebate_cap",
      "file": "settlement.py",
      "line": 39,
      "assertion": "Discount boundary is subtotal >= 15000 (GROWTH 10%, ENTERPRISE 20% else 10%); cap rebate_applied = min(fee_rebate, after_discount)."
    },
    {
      "test": "test_statement_format_and_signed_zero",
      "file": "statement.py",
      "line": 11,
      "assertion": "Use '#' delimiter ({merchant_id}#{currency}:) and preserve leading '-' on zero discount/rebate (TIER_DISCOUNT: -0, REBATE_APPLIED: -0)."
    }
  ]
}

Because all four failure root causes were immediately visible without scrolling through 76 passing tests, Mode B fixed all three files in a single Turn 2 call (3,030 input tokens)—eliminating the intermediate rework turn entirely.

labjudge16) Both modes passed all 20 hidden unit and E2E test cases verified by labjudge16:

# Test Case ID Invariant / Boundary Verified Mode A ( Raw CLI ) Mode B ( Token Killer )
1–6 validator_01 tovalidator_06 Trim/uppercase, non-mutation, JPY ,bool rejection,0 cents &1000 bps bounds 6 / 6 PASS 6 / 6 PASS
7–15 settlement_01 tosettlement_09 First-seen order, conflict check, 300 vs 301 bps,14999 vs 15000 tier, rebate cap 9 / 9 PASS 9 / 9 PASS
16–20 e2e_01 toe2e_05 # separator, signed zero-0 ,$0 auth statement, line order, error propagation 5 / 5 PASS 5 / 5 PASS
Total 20 Hidden Cases — 20 / 20 (100%) 20 / 20 (100%)

Never wire raw subprocess.run(["pytest"]) stdout directly into your LLM agent's tool response. Instead:

pytest --tb=short -q --no-header --no-summary (or pytest-json-report) inside your tool wrapper."passed": 500 as a single integer plus the exact file, line number, and assertion diff for the 2 failures.rtk (Rust Token Killer) or a 30-line Python wrapper around your test runner immediately cut agent token bills by E20): MEMORY.md``E21):

── more in #ai-agents 4 stories · sorted by recency
── more on @ai labo 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/stop-dumping-raw-pyt…] indexed:0 read:6min 2026-11-01 · —