<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Terminal-Bench Harness on RockB</title><link>https://baeseokjae.github.io/tags/terminal-bench-harness/</link><description>Recent content in Terminal-Bench Harness on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 13 Oct 2026 00:16:00 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/terminal-bench-harness/index.xml" rel="self" type="application/rss+xml"/><item><title>Do Local Coding Models Actually Finish Agent Tasks? A Reproducible Task-Success Protocol</title><link>https://baeseokjae.github.io/posts/local-coding-model-agent-task-success-2026/</link><pubDate>Tue, 13 Oct 2026 00:16:00 +0000</pubDate><guid>https://baeseokjae.github.io/posts/local-coding-model-agent-task-success-2026/</guid><description>Leaderboards report scaffolds and retries, not end-state completion. A frozen four-task harness that scores a local model completed / partially / failed, with the raw matrix, transcripts and a fixture self-check you can rerun.</description><content:encoded><![CDATA[<p>Choosing between a local open-weight coding model and a managed agent is usually argued with the wrong number. Throughput, price per token and leaderboard position are all published and all easy to compare; none of them answers the question that decides the purchase: on this kind of task, on hardware like mine, how often does the model actually finish before it stalls, loops or asks for help?</p>
<p>This article ships a small protocol that measures exactly that, and reports no model results of its own. It publishes a frozen four-task sandbox, a harness with a documented adapter contract, a scoring sheet and a fixture self-check whose raw output is recorded and hashed. The model-by-task matrix in the middle of this page is filled with <code>not run</code>, because no model was executed while writing it. That is the honest state of the evidence: the instrument is built and verified, the vendor numbers it compares against are third-party, and the first real row is generated by whoever points it at a served model.</p>
<h2 id="the-question-a-leaderboard-cannot-answer">The question a leaderboard cannot answer</h2>
<p>Two published results are enough to show why the leaderboard number does not transfer. Moonshot&rsquo;s own model card for Kimi K2 reports 65.8% pass@1 on SWE-bench Verified <em>with bash/editor tools, single-attempt patches and no test-time compute</em>, 47.3% on SWE-bench Multilingual under the same conditions, and 71.6% on the same benchmark when parallel test-time compute is used to sample several sequences and pick the best with an internal scoring model (<a href="https://huggingface.co/moonshotai/Kimi-K2-Instruct/raw/main/README.md">Kimi K2 model card</a>). The first and third numbers are the same model on the same benchmark: the difference is the harness around it.</p>
<p>The Aider polyglot leaderboard makes the same point with retries. Its raw data file lists, for the same 225 test cases and the diff edit format, first-attempt and two-attempt pass rates of 14.2% / 40.0% for Qwen3 32B, 13.8% / 41.8% for gpt-oss-120b (high), 20.4% / 59.1% for Kimi K2 and 38.7% / 70.2% for DeepSeek-V3.2-Exp (Chat) (<a href="https://raw.githubusercontent.com/Aider-AI/aider/main/aider/website/_data/polyglot_leaderboard.yml">Aider polyglot leaderboard data</a>). A single number quoted from that table describes a retry policy as much as it describes a model.</p>
<table>
	<thead>
			<tr>
					<th>Model (as published)</th>
					<th>Evaluation</th>
					<th>First attempt</th>
					<th>With a second attempt and test feedback</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Kimi K2</td>
					<td>SWE-bench Verified, vendor bash/editor scaffold</td>
					<td>65.8% pass@1, single attempt, no test-time compute</td>
					<td>71.6% pass@1 with parallel test-time compute and an internal best-of scorer</td>
			</tr>
			<tr>
					<td>Qwen3 32B</td>
					<td>Aider polyglot, diff format, 225 cases</td>
					<td>14.2%</td>
					<td>40.0%</td>
			</tr>
			<tr>
					<td>gpt-oss-120b (high)</td>
					<td>Aider polyglot, diff format, 225 cases</td>
					<td>13.8%</td>
					<td>41.8%</td>
			</tr>
			<tr>
					<td>Kimi K2</td>
					<td>Aider polyglot, diff format, 225 cases</td>
					<td>20.4%</td>
					<td>59.1%</td>
			</tr>
			<tr>
					<td>DeepSeek-V3.2-Exp (Chat)</td>
					<td>Aider polyglot, diff format, 225 cases</td>
					<td>38.7%</td>
					<td>70.2%</td>
			</tr>
	</tbody>
</table>
<p>Every cell above is a third-party or vendor figure, reproduced with its own harness and retry policy attached, and the Aider data file&rsquo;s most recent rows are dated 2025-10-03. That file also records how its runs were served: each of the four rows reproduced above carries a hosted provider route in its <code>command:</code> field — <code>aider --model openrouter/qwen/qwen3-32b</code>, <code>openrouter/moonshotai/kimi-k2</code>, <code>openrouter/openai/gpt-oss-120b --reasoning-effort high</code> and <code>deepseek/deepseek-chat</code> — 11 rows in the file carry an <code>openrouter/</code> command, and no row names a local endpoint, so the file contains no locally served comparison. Note what the table does not contain: a column for &ldquo;finished the task on my machine, once, with no second attempt&rdquo;. That column does not exist in public reporting, and it is the one that decides a local deployment.</p>
<h2 id="why-the-harness-not-the-weights-produces-the-number">Why the harness, not the weights, produces the number</h2>
<p>The strongest published statement on this is also the oldest cited here. The SWE-agent paper attributes its state-of-the-art result — 12.5% pass@1 on SWE-bench and 87.7% on HumanEvalFix at the time — to its agent-computer interface rather than to a different base model, and argues explicitly that interface design changes agent behaviour and performance (<a href="https://arxiv.org/abs/2405.15793">SWE-agent, arXiv:2405.15793</a>). Read that as a measurement warning rather than a historical note: two harnesses wrapping the same weights are two different systems, and 2026 harnesses differ far more than the 2024 ones did.</p>
<h2 id="why-completion-has-to-be-verified-per-task">Why completion has to be verified per task</h2>
<p>Terminal-style agent benchmarks took the right structural decision. Terminal-Bench ships a task dataset <em>and</em> an execution harness, each task carries a test script that verifies whether the agent completed it and an oracle reference solution, and the project self-describes as beta with roughly 100 tasks (<a href="https://raw.githubusercontent.com/laude-institute/terminal-bench/main/README.md">Terminal-Bench README</a>). Machine-verified completion, per task, with an oracle to check the checker. The SWE-bench family does the same at larger scale: SWE-bench Verified is a 500-problem subset that real software engineers have confirmed are solvable, evaluated in a fully containerised Docker harness built for reproducible runs (<a href="https://raw.githubusercontent.com/SWE-bench/SWE-bench/main/README.md">SWE-bench README</a>).</p>
<p>Both are the right shape and neither is the number you need. They are fixed, shared task sets scored on a common harness: useful for ranking the field, silent about whether the model that lands at position 20 in such a ranking can complete <em>your</em> three-file refactor on the GPU you own, at the quantisation that fits in your VRAM.</p>
<h2 id="the-gap-between-benchmark-success-and-shipped-work">The gap between benchmark success and shipped work</h2>
<p>The cautionary counterpoint is a randomised controlled trial. METR ran 16 experienced open-source developers through 246 real issues in repositories they maintain, and found that when AI tools were allowed the developers took 19% longer to finish, while the same developers had forecast a 24% speedup and still believed they had been sped up by 20% after the fact; METR argues that benchmarks may overstate capability because their tasks are self-contained and algorithmically scored (<a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/">METR study</a>). That trial used frontier hosted models and a small developer sample, so it says nothing specific about local open-weight models. It says something about measurement: self-reported productivity and benchmark position both diverge from observed end-state completion, which is the quantity this protocol records.</p>
<h2 id="the-protocol-you-can-run">The protocol you can run</h2>
<p>Everything below is published as files in the site repository, and it is small on purpose — four tasks, one sandbox, one harness, one scoring sheet:</p>
<ul>
<li><strong>Frozen task set</strong> — <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/tasks.json">tasks.json</a>: edit a method, add a test module, fix a failing test, rename a method across every file. Each task declares its own scoped test suite, and the dataset carries a single 12-step budget that the harness applies per task.</li>
<li><strong>Frozen sandbox</strong> — <a href="https://github.com/baeseokjae/baeseokjae.github.io/tree/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/sandbox">sandbox/</a>: a synthetic <code>inventory</code> package with a green <code>tests/test_store.py</code> and one deliberately failing <code>tests/test_report.py</code>, hashed as a tree in every run header.</li>
<li><strong>Harness</strong> — <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/harness.py">harness.py</a>: the adapter contract, the run loop, the per-task verifiers and the scorer.</li>
<li><strong>Adapters</strong> — <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/models.py">models.py</a>: the deterministic doubles plus <code>LocalOpenAICompatAdapter</code>, which drives any OpenAI-compatible <code>/v1/chat/completions</code> endpoint (vLLM, llama.cpp server, Ollama <code>/v1</code>, LM Studio, TGI).</li>
<li><strong>Runner and score sheet</strong> — <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/run_tasks.py">run_tasks.py</a> writes <code>transcript.json</code> (the sanitized step log), <code>matrix.csv</code> (the scoring sheet) and <code>result.json</code> (run header with the task-set hash, the sandbox tree hash and the aggregates).</li>
<li><strong>Self-check</strong> — <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/test_harness.py">test_harness.py</a> and <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/fixture_report.py">fixture_report.py</a>.</li>
</ul>
<p>An adapter is one class with one method:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">TaskAdapter</span>:
</span></span><span style="display:flex;"><span>    name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;model-id + quantisation + endpoint&#34;</span>      <span style="color:#75715e"># recorded in the run header</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">next_action</span>(self, observation) <span style="color:#f92672">-&gt;</span> dict: <span style="color:#f92672">...</span>   <span style="color:#75715e"># one action per call</span>
</span></span></code></pre></div><p>It receives the task, the step index, the current repository as <code>{relpath: text}</code>, the last suite output and the steps left, and returns exactly one action: <code>write</code>, <code>patch</code>, <code>run_tests</code>, <code>escalate</code> or <code>stop</code>. <code>run_tests</code> returns feedback, which is what separates a single-shot number from a retried one — the scorer counts those runs so the retry budget is visible in the result instead of hidden inside it.</p>
<p>Scoring is per task and end-state, in three buckets: <code>completed</code> when every scored check passes, <code>partially</code> when at least one does (the method exists but the suite is red), <code>failed</code> when none does or the adapter escalated. The suite-success guards are recorded but deliberately excluded from the score, because the frozen <code>tests/test_store.py</code> is already green and &ldquo;the suite passes&rdquo; is not evidence that a task advanced.</p>
<p>Run it with three commands, standard library only:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>python3 test_harness.py                                              <span style="color:#75715e"># harness unit tests</span>
</span></span><span style="display:flex;"><span>python3 run_tasks.py --adapter reference --out <span style="color:#e6db74">&#34;</span>$TMPDIR<span style="color:#e6db74">/out&#34;</span>         <span style="color:#75715e"># offline control</span>
</span></span><span style="display:flex;"><span>python3 run_tasks.py --adapter local --base-url http://127.0.0.1:8000 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>        --model &lt;served-model-id&gt; --out <span style="color:#e6db74">&#34;</span>$TMPDIR<span style="color:#e6db74">/out&#34;</span>                <span style="color:#75715e"># a real model</span>
</span></span></code></pre></div><h2 id="fixture-self-check-proving-the-harness-not-measuring-a-model">Fixture self-check: proving the harness, not measuring a model</h2>
<p>A scorer is only worth reading if a known defect moves its own column and nothing else, so the package ships five deterministic offline doubles and their recorded output. Running <code>python3 fixture_report.py</code> on 2026-10-13 (UTC) with Python 3.12.3 reported <code>Ran 10 tests in &lt;elapsed&gt;s</code> and <code>OK</code> — all ten harness tests passing, captured as <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/fixture-tests.txt">fixture-tests.txt</a> (sha256 <code>55561cd37a0ba0f9a7b536b1342e5d849d08851866a675c8c46945cf53aa99aa</code>), with the full score sheet, environment and code hashes in <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/protocol/fixture-report.json">fixture-report.json</a> and the run&rsquo;s environment, commands and hashes in <a href="https://github.com/baeseokjae/baeseokjae.github.io/blob/main/research/evidence/local-coding-model-agent-task-success-2026/first-party-run.json">first-party-run.json</a>.</p>
<p>The four doubles behaved as designed on that run. The reference double completed 4 of 4 tasks in a mean 0.143 s of harness time per task with 5 test runs and no escalations. The stall double (stops immediately) and the loop double (spins until the 12-step budget is exhausted, 48 test runs total) each scored 0 completed / 0 partially / 4 failed. The escalation double scored 0 completed and recorded exactly one escalation per task. The partial-rename double scored 3 completed and 1 partially, with the partially-scored cell being only the rename task, where the definition was renamed and the call sites were not.</p>
<p>That is the whole claim: a synthetic control scores 4 of 4, a single-defect double loses only its own task. These are fixture results produced by scripted in-process doubles to validate the instrument; no model was loaded, no GPU time was used, and not one of these numbers measures Qwen, Kimi, gpt-oss, DeepSeek or any other system.</p>
<h2 id="results-not-run">Results: not run</h2>
<p>The matrix this article exists to publish is empty, and it says so cell by cell.</p>
<table>
	<thead>
			<tr>
					<th>Model slot</th>
					<th>Quantisation / context</th>
					<th>T1 edit</th>
					<th>T2 add test</th>
					<th>T3 fix failing test</th>
					<th>T4 rename across files</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Model A (largest that fits the GPU)</td>
					<td>not run</td>
					<td>not run</td>
					<td>not run</td>
					<td>not run</td>
					<td>not run</td>
			</tr>
			<tr>
					<td>Model B (smaller / more aggressive quant)</td>
					<td>not run</td>
					<td>not run</td>
					<td>not run</td>
					<td>not run</td>
					<td>not run</td>
			</tr>
	</tbody>
</table>
<p>No local model was executed for this article. Filling a cell requires a served model, a recorded endpoint and sampling parameters, a transcript and the sandbox tree hash — otherwise the number is an anecdote with a table around it. Until those exist, the row is <code>not run</code>.</p>
<h2 id="failure-taxonomy-what-stalling-looks-like">Failure taxonomy: what stalling looks like</h2>
<p>The instrument is useful mainly because the failure modes are distinguishable, and each one maps to a column rather than a vibe:</p>
<ul>
<li><strong>No progress</strong> — the adapter stops immediately or edits files that no check reads. Shows up as <code>steps</code> low and every scored check false.</li>
<li><strong>Looping</strong> — the same action repeats until the step budget is exhausted. Shows up as <code>steps</code> at the budget with a high <code>test_runs</code> count and red checks.</li>
<li><strong>Red suite after an edit</strong> — the method exists but the suite fails: <code>partially</code>, and the failing test name is in the transcript.</li>
<li><strong>Partial refactor</strong> — the rename lands in the definition and not in the call sites. This is the single most common way a multi-file task &ldquo;succeeds&rdquo; in a transcript and fails in a repository, and it is why the rename task checks call sites separately from the definition.</li>
<li><strong>Escalation</strong> — the model asks for help. Counted, not hidden: a high escalation count with a good partial rate is a different product decision from a low completion rate with none.</li>
</ul>
<h2 id="reproducing-this-on-your-own-hardware">Reproducing this on your own hardware</h2>
<ol>
<li>Pin the sandbox: copy the tree, record <code>harness.tree_sha256(sandbox)</code> before the first run, and never edit it in place.</li>
<li>Serve the model and record the endpoint, model id, quantisation, context length and sampling parameters in the adapter&rsquo;s <code>name</code>. Those strings are the run header; a result without them is not reproducible.</li>
<li>Implement <code>next_action</code> against the endpoint&rsquo;s chat API, and keep temperature at zero with a fixed seed for the first run so the variance you see is the harness, not the sampler.</li>
<li>Add tasks rather than changing them. Each new task needs a scored check that a script can evaluate and a suite scope that no unrelated frozen failure can mask.</li>
<li>Publish the transcript and the matrix together. One headline completion rate hides which of the five failure modes you are paying for; the transcript is what lets a second person disagree with the score.</li>
</ol>
<p>If your VRAM cannot serve the model you want to test, change the quantisation and record it — the point of the protocol is that the number belongs to a configuration, and an honest smaller configuration beats an unattainable bigger one.</p>
<h2 id="when-a-managed-agent-is-still-the-right-call">When a managed agent is still the right call</h2>
<p>Two quantities decide it, and this protocol produces both. Escalations per task is the first: a local model that completes most tasks but escalates on the remainder still needs a human in the loop, and that human&rsquo;s time is the real cost. Test runs per task is the second: the gap between the first-attempt and second-attempt columns in the published tables above is the price of feedback, and on a local rig that price is paid in wall-clock time rather than in tokens. Where a managed agent remains cheaper per completed task under those two measures, that is a measurement result, not a preference — and the harness is what makes the comparison fair, because it scores both sides on the same frozen tasks with the same verifier.</p>
<h2 id="limitations-and-what-this-protocol-cannot-tell-you">Limitations and what this protocol cannot tell you</h2>
<ul>
<li><strong>No first-party model result exists here.</strong> The matrix is <code>not run</code>; every external figure is attributed to its own harness, date and retry policy.</li>
<li><strong>The sandbox is synthetic and sequential.</strong> Tasks run in one working copy in order, so an early failure is inherited by later tasks; the sandbox is a controlled fixture, not a snapshot of a real repository.</li>
<li><strong>A four-task set is a probe, not a score.</strong> It detects &ldquo;does this model finish this kind of work&rdquo;, not a percentage to quote.</li>
<li><strong>Borrowed numbers are never ours.</strong> The Kimi K2, Aider polyglot, Terminal-Bench, SWE-bench and METR figures are the vendors&rsquo; and authors&rsquo; results on their own harnesses, reproduced here with their scope attached.</li>
<li><strong>Demand evidence is directional only.</strong> The topic&rsquo;s measured demand is a small adjacent query row in the operator&rsquo;s own search analytics plus adjacent serving-capacity queries; no search volume, difficulty or conversion number is asserted.</li>
<li><strong>Unknown cost stays unknown.</strong> No monetary cost is claimed anywhere in this article; no paid provider was used and no price estimate is inferred from a zero balance.</li>
</ul>
<h2 id="primary-sources">Primary sources</h2>
<p>Fetched and re-checked live on 2026-10-13 (UTC) while writing. The response bodies were saved and hashed as a private local author verification during this article&rsquo;s own research; they are third-party downloads and are deliberately not republished. The files a reader can obtain are the original protocol artifacts linked above, together with the recorded self-check output.</p>
<ul>
<li>Kimi K2 model card (65.8% SWE-bench Verified with bash/editor tools; 47.3% Multilingual; 71.6% with parallel test-time compute) — <a href="https://huggingface.co/moonshotai/Kimi-K2-Instruct/raw/main/README.md">https://huggingface.co/moonshotai/Kimi-K2-Instruct/raw/main/README.md</a></li>
<li>SWE-agent paper (agent-computer interface design; pass@1 12.5% SWE-bench, 87.7% HumanEvalFix) — <a href="https://arxiv.org/abs/2405.15793">https://arxiv.org/abs/2405.15793</a></li>
<li>Aider polyglot leaderboard data (first-attempt vs two-attempt pass rates, diff format, 225 cases) — <a href="https://raw.githubusercontent.com/Aider-AI/aider/main/aider/website/_data/polyglot_leaderboard.yml">https://raw.githubusercontent.com/Aider-AI/aider/main/aider/website/_data/polyglot_leaderboard.yml</a></li>
<li>Terminal-Bench README (task dataset plus execution harness, per-task test script, oracle solution, beta ~100 tasks) — <a href="https://raw.githubusercontent.com/laude-institute/terminal-bench/main/README.md">https://raw.githubusercontent.com/laude-institute/terminal-bench/main/README.md</a></li>
<li>SWE-bench README (500-problem Verified subset, containerised Docker harness) — <a href="https://raw.githubusercontent.com/SWE-bench/SWE-bench/main/README.md">https://raw.githubusercontent.com/SWE-bench/SWE-bench/main/README.md</a></li>
<li>METR randomised controlled trial (16 developers, 246 issues, 19% slowdown) — <a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/">https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/</a></li>
</ul>
]]></content:encoded></item></channel></rss>