RTK × Agent Harness — Efficiency Architecture
Central thesis: The harness ladder tells you what infrastructure makes agents reliable. RTK tells you how cheaply you can run it. Together they make the case that a small local model + a strong harness + compressed tool output can match or beat a large cloud model with no harness — at a fraction of the cost.
The Problem RTK Solves Inside a Multi-Agent Pipeline
The multi_agent_coding pipeline has four nodes:
PLANNER → CODER → EVALUATOR → DEBUGGER → EVALUATOR (retry loop)
Every LLM node receives a context window built from:
- the original task description
- prior agent outputs (plan, code)
- tool call returns — and this is where cost explodes
The EVALUATOR runs pytest --tb=short and hands the raw output to the DEBUGGER. On any
realistic test suite, that raw pytest output is between 2,000 and 13,000 tokens of tracebacks,
assertion diffs, file paths, and boilerplate headers. The DEBUGGER only needs the assertion
lines and failed test names — maybe 100–200 tokens of signal. The rest is noise the LLM
has to wade through on every debug iteration.
Raw pytest output is the single most compressible content type in any coding agent pipeline, and the DEBUGGER is the node that burns the most tokens because it runs up to three times. That is exactly where RTK pays off.
Where RTK Plugs Into the Harness (3 Integration Points)
Integration Point 1 — run_tests / _run_tests ← highest value
Both projects run pytest as a subprocess and return raw output.
harness_ladder/tools/factory.py, line 89:
# Current
result = subprocess.run(
["python", "-m", "pytest", str(test_file), "-v", "--tb=short", "--no-header"],
...
)
# RTK-wired (one token change)
result = subprocess.run(
["rtk", "python", "-m", "pytest", str(test_file), "-v", "--tb=short", "--no-header"],
...
)
multi_agent_coding/system.py, _run_tests():
# Current
result = subprocess.run(
["python", "-m", "pytest", tests_path, "-v", "--tb=short", "--no-header"],
...
)
# RTK-wired
result = subprocess.run(
["rtk", "python", "-m", "pytest", tests_path, "-v", "--tb=short", "--no-header"],
...
)
Nothing else changes. The DEBUGGER still reads from state["test_output"]. It just reads
compressed output instead of raw tracebacks. The existing _parse_failures() handwritten
filter in system.py becomes redundant — RTK's pytest filter does the same job
deterministically, without bespoke regex.
Integration Point 2 — read_file tool output (harness_ladder)
When the agent calls read_file("solution.py") to inspect code it previously wrote, the
full file content enters the context window. For any non-trivial implementation (50+ lines),
this adds 200–800 tokens per read call, and agents call it repeatedly across iterations.
RTK's rtk cat solution.py applies its code-aware compressor, preserving function
signatures and reducing body noise. Not as dramatic as the pytest savings, but meaningful
across a multi-rung sweep.
Integration Point 3 — state["test_output"] (multi_agent_coding)
In system.py, the EVALUATOR writes raw pytest output into state["test_output"], which
the DEBUGGER reads directly. By compressing at the subprocess level (Point 1 above), this
state field automatically carries compressed content — no state schema changes required.
Token Math Per Agent Node
The following breakdown uses numbers measured against this repo. "Real suite" refers to a medium-complexity task (email validator with 8 test cases, ~60 lines of implementation).
| Node | Context without RTK | Context with RTK | Delta |
|---|---|---|---|
| PLANNER | ~500 tokens (task only) | same | — |
| CODER | ~900 tokens (task + plan) | same | — |
| EVALUATOR | no LLM — deterministic | no LLM — deterministic | — |
| DEBUGGER (1 call, mock suite) | 687 tokens | 88 tokens | −87.2% |
| DEBUGGER (1 call, real suite) | 8,000–13,000 tokens | ~231 tokens | −97–98% |
| DEBUGGER (×3 iterations) | context accumulates each round | context stays flat | no bloat |
The compounding effect across iterations is the non-obvious win. Each DEBUGGER retry reloads the full context: task + code + all prior tool outputs. Without RTK, the message history grows by 8,000+ tokens per iteration. With RTK, each iteration adds ~231 tokens. By iteration 3, the difference is a 24,000-token gap in the running context.
The Model Routing Implication
The DEBUGGER's large context requirement was the forcing function that made cloud models
feel necessary. The reasoning: qwen2.5:7b handles 8,000-token contexts poorly — it
loses track of the assertion lines buried in verbose pytest output, produces hallucinated
fixes, and fails more often than it should. The instinct is to reach for claude-sonnet-4-6
or a 14B+ local model for the DEBUGGER specifically.
RTK removes that forcing function:
Node Without RTK With RTK
──────────────────────────────────────────────────────────────────
PLANNER qwen2.5:7b ✓ (short ctx) qwen2.5:7b ✓ (unchanged)
CODER qwen2.5:7b ✓ (medium ctx) qwen2.5:7b ✓ (unchanged)
EVALUATOR no LLM no LLM
DEBUGGER needs 14B+ or cloud API ✗ qwen2.5:7b ✓ (88 tokens)
With RTK, the entire multi_agent_coding pipeline runs on qwen2.5:7b locally at no cost.
Without RTK, the DEBUGGER becomes the bottleneck that either bloats memory (large local
model) or introduces API cost (cloud model).
The efficiency ratio at Claude Sonnet 4.6 input pricing ($3/MTok):
| Scenario | DEBUGGER tokens × 3 iterations | API cost | Local model cost |
|---|---|---|---|
| No RTK, cloud model | ~36,000 tokens | ~$0.11 | — |
| RTK, local qwen2.5:7b | ~693 tokens | $0.00 | $0.00 |
That $0.11 is per task, per pipeline run. At 100 tasks/day it's $11/day. With RTK and local inference it's $0.00/day. The savings compound with volume.
Rung 6 — RTK as a Measurable Harness Layer
The harness ladder (rungs 0–5) measures capability additions:
| Rung | What's added | What it measures |
|---|---|---|
| 0 | bare ReAct loop | baseline |
| 1 | planning tools | structural decomposition |
| 2 | scratch memory | context offload |
| 3 | forced verify node | harness-enforced feedback loop |
| 4 | sandbox constraints | Ashby's Law in action |
| 5 | sub-agent delegation | isolated context per sub-problem |
| 6 | RTK tool-output compression | cost/efficiency per rung |
Rungs 0–5 answer: does adding this layer improve pass rate? Rung 6 asks a different question: what does each rung cost, and does RTK change the cost/quality tradeoff enough to make a cheaper model viable?
The 3-axis benchmark this enables
Run the eval sweep across three configurations:
A: (rung=N, model=qwen2.5:7b, rtk=OFF) ← local, no compression
B: (rung=N, model=qwen2.5:7b, rtk=ON) ← local, RTK-compressed
C: (rung=N, model=claude-sonnet-4-6, rtk=OFF) ← cloud baseline
The hypothesis: B matches or beats C on pass rate at rung 3+, at zero API cost.
The mechanism: at rung 3, the harness forces the verify-and-retry loop regardless of model
quality. The model can't skip it. With RTK keeping the DEBUGGER's context small and clean,
qwen2.5:7b doesn't fail because of context noise — it fails because of reasoning gaps,
which are a different problem. The rung 3 harness compensates for reasoning gaps by forcing
retries. RTK compensates for context size limitations by compressing inputs. The combination
is what makes the small model viable.
This is "harness engineering > model engineering" with a cost dimension attached.
Implementation sketch for Rung 6
Add an rtk: bool flag to build_agent() and get_tools():
# harness_ladder/tools/factory.py
def _test_tool(task_dir: Path, rtk: bool = False) -> BaseTool:
cmd_prefix = ["rtk"] if rtk else []
@make_tool
def run_tests() -> str:
"""Run the pytest test suite. Returns compressed output if RTK is enabled."""
result = subprocess.run(
cmd_prefix + ["python", "-m", "pytest", str(test_file), "-v", "--tb=short", "--no-header"],
capture_output=True, text=True, cwd=str(task_dir), timeout=30,
)
return (result.stdout + result.stderr).strip()
return run_tests
def get_tools(rung: int, task_dir: Path, model=None, rtk: bool = False) -> list:
tools = _file_tools(task_dir, sandboxed=(rung >= 4))
tools.append(_test_tool(task_dir, rtk=rtk))
...
And propagate it through build_agent() and run_experiment.py:
# run_experiment.py
for model_name in MODELS:
for rung in RUNGS:
for rtk in [False, True]: # ← sweep both configurations
agent = build_agent(model, rung, task_dir, rtk=rtk)
result = agent.invoke(...)
record(model_name, rung, rtk, task_id, result["passed"])
The eval output then becomes a table with three axes instead of two, and the rtk=True
column is the cost story.
Why This Matters Beyond Cost
RTK compression does two things simultaneously:
1. Reduces token count — directly reduces API cost and local inference time.
2. Improves signal-to-noise ratio — the DEBUGGER reads 88 tokens of clean assertion failures rather than 8,000 tokens of mixed signal and boilerplate. A smaller, denser input is often easier for a small model to act on correctly than a large noisy one.
This is the non-obvious efficiency gain: RTK doesn't just make the pipeline cheaper, it
makes the DEBUGGER's job structurally easier. It is doing a version of what _parse_failures()
in system.py does manually — but transparently, consistently, and across every command
type that RTK covers (100+ commands: git, pytest, cargo, docker, kubectl, aws, etc.).
The pattern generalizes to any multi-agent system where tool outputs dominate context:
- A research agent whose subagents read large docs → Headroom on RAG chunks
- A DevOps agent running kubectl/docker commands → RTK on every subprocess call
- A code review agent running linters → RTK on eslint, tsc, clippy, ruff output
The harness enforces correctness. RTK makes it affordable.
Connection to the Deep Research Agent
The deep_research_agent uses DuckDuckGo search as its primary tool. Search results are
prose — RTK doesn't touch prose. That's where Headroom's Kompress layer applies (see
rtk-vs-headroom.md in rust_token_killer/). But if the research agent is extended to
run shell commands (e.g., querying internal CLIs, running scripts to gather data), RTK
applies immediately.
The broader principle: RTK owns shell output, Headroom owns everything else. In a full multi-agent system, you want both layers running simultaneously — RTK at the subprocess boundary, Headroom at the RAG/message-history boundary.
Implementation Roadmap
Immediate (one-liner): Wire RTK into _run_tests / run_tests in both projects.
No architecture changes. Measure DEBUGGER context size before/after on a real task.
Short term: Add rtk: bool to build_agent() and get_tools() in harness_ladder.
Run the 3-axis eval sweep. Produce the pass-rate vs. cost table.
Medium term: Add a model_router node between EVALUATOR and DEBUGGER that reads
context size from the EVALUATOR output and selects:
- RTK-compressed output →
qwen2.5:7b(local, free) - Uncompressed output above threshold →
claude-sonnet-4-6(cloud, reliable)
This is the practical production pattern: RTK runs always, the model routes based on what the compressed context still demands.
Summary
| Lever | Effect | Mechanism |
|---|---|---|
| Harness rung 3+ | Enforces verify-retry loop regardless of model | LangGraph node the model cannot skip |
RTK on run_tests |
Compresses pytest output 87–98% | Rust filter at subprocess boundary |
| Combined | Small model (qwen2.5:7b) viable for full pipeline | Harness compensates for reasoning gaps; RTK compensates for context noise |
| Cost outcome | $0.00/task vs $0.11/task (DEBUGGER alone) | Local model + compressed context |
The harness makes small models work. RTK makes them affordable.
Filed under: Harness Engineering · Context Compression · Model Routing · Cost Optimization