DEV Community

Avery Li
Avery Li

Posted on

The Swap Harness: What a Senior Asked Before the Team Trusted a Free-Tier Coding Assistant

The decision that survived the pairing session was narrow and slightly boring: a free-tier coding assistant may draft code and test scaffolding for this team, but nothing it produces reaches a merge request until it clears a swap harness first. The harness is about eighty lines of Python, one manifest writer, and a decision table with abandon criteria written down before any prompt was sent. It exists because the senior on the call refused to argue about model quality in prose and asked for something cheaper than an opinion.

The assistant under evaluation was MonkeyCode, an open-source coding assistant project whose operator states that it offers free model access along with a free server option for running it. The same outreach materials describe a free allowance on the order of ten million tokens, which is a figure that moves with plan changes and therefore has to be re-checked against current documentation on the day it actually matters.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

No model names, latency figures, throughput numbers, or acceptance rates appear below, because the entire point of the workflow is to survive alias changes and because inventing those numbers would be worse than omitting them. Everything here is a method plus an adapter seam the reader fills in.

The three questions that set the agenda

The senior opened with three reported questions rather than a verdict, and each one collapsed a category of vague complaint into something testable. The first was which part of a generated artifact the team actually verifies, since reviewing a diff by eye is not verification. The second was what specific observation would make the team stop using the free tier, asked before any enthusiasm had accumulated. The third was who signs off on a run that nobody can reproduce next month.

Those three questions produced three artifacts, in order: a predicate list per case, an abandon-criteria column, and a run manifest. The pairing session then spent the remaining hour deleting anything that did not feed one of them.

Three dead ends, hit in the order they occurred

The first dead end was the eyeball benchmark, where the same prompt went to two backends and the pair compared the outputs side by side. It felt informative and produced nothing durable, because a sample size of one with no recorded prompt hash cannot be re-examined or disputed later.

The second dead end was pinning a model alias in the README as if documentation were a lockfile. Aliases drift, endpoints get repointed, and a reader six weeks later inherits a claim with no receipt attached to it.

The third dead end was letting the test file call a vendor client directly, which guarantees that every future swap becomes a refactor instead of a configuration change. The pair kept the adapter seam and threw away the rest of that afternoon's work.

Step 1: Write the run manifest before writing any prompt

The manifest is the artifact that makes a later conversation possible, and it costs about thirty lines. It records what ran, against which host and alias, on which commit, at which time, so that a green result cannot be silently attributed to the wrong backend.

# manifest.py — requires Python 3.11+ for datetime.UTC
import datetime
import hashlib
import json
import os
import platform
import subprocess


def sha(text: str) -> str:
    return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]


def build_manifest(prompt: str, provider: dict, suite: str) -> dict:
    return {
        "suite": suite,
        "prompt_sha": sha(prompt),
        "prompt_chars": len(prompt),
        "provider_label": provider["label"],
        "endpoint_host": provider["host"],
        "model_alias": provider["alias"],
        "python": platform.python_version(),
        "platform": platform.platform(),
        "git_sha": subprocess.check_output(
            ["git", "rev-parse", "--short", "HEAD"], text=True
        ).strip(),
        "recorded_at_utc": datetime.datetime.now(datetime.UTC).isoformat(timespec="seconds"),
    }


def write_manifest(manifest: dict, path: str) -> None:
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w", encoding="utf-8") as fh:
        json.dump(manifest, fh, indent=2, sort_keys=True)
Enter fullscreen mode Exit fullscreen mode

Step 2: Run the same cases against two backends

The suite is a parameterized pytest file where providers come from environment variables and cases come from a JSON file. The adapter and checker fixtures are deliberately not shown, because they are the only vendor-specific part and the reader owns them.

# tests/test_swap_conformance.py
import json
import os

import pytest

from manifest import build_manifest, write_manifest

PROVIDERS = [
    {"label": "free-tier", "host": os.environ.get("FREE_HOST", "unset"),
     "alias": os.environ.get("FREE_ALIAS", "unset")},
    {"label": "baseline", "host": os.environ.get("BASE_HOST", "unset"),
     "alias": os.environ.get("BASE_ALIAS", "unset")},
]

CASES = json.load(open("cases/drafting.json", encoding="utf-8"))


@pytest.mark.parametrize("provider", PROVIDERS, ids=[p["label"] for p in PROVIDERS])
@pytest.mark.parametrize("case", CASES, ids=[c["id"] for c in CASES])
def test_swap_conformance(provider, case, adapter, checker):
    if provider["host"] == "unset":
        pytest.skip(f"{provider['label']} endpoint is not configured")
    manifest = build_manifest(case["prompt"], provider, suite="drafting")
    write_manifest(manifest, f"artifacts/{case['id']}-{provider['label']}.json")
    raw = adapter.call(provider, case["prompt"])
    normalized = adapter.normalize(raw)
    assert checker(normalized, case["expect"]), (
        f"{case['id']} failed on {provider['label']}; prompt_sha={manifest['prompt_sha']}"
    )
Enter fullscreen mode Exit fullscreen mode

Cases carry structural predicates instead of prose expectations, which keeps them stable across phrasing changes. A representative case file looks like this:

[
  {
    "id": "retry-helper",
    "prompt": "Write a Python function retry(fn, attempts=3) that retries on exception and raises the last error.",
    "expect": {
      "must_include": ["def retry", "raise"],
      "must_not_include": ["except:", "time.sleep(0)"],
      "max_lines": 40
    }
  }
]
Enter fullscreen mode Exit fullscreen mode

The run itself is two exports and one command, and the artifacts directory becomes the evidence trail. A run with an unset host skips loudly rather than passing quietly, which the senior insisted on.

export FREE_HOST=<free-endpoint-host> FREE_ALIAS=<alias-from-current-docs>
export BASE_HOST=<comparison-host>   BASE_ALIAS=<alias-from-current-docs>

python -m pytest tests/test_swap_conformance.py -q --maxfail=1
ls artifacts/ | head
Enter fullscreen mode Exit fullscreen mode

Step 3: Normalize before comparing, never after

Normalization lives inside the adapter, so no comparison code ever sees raw model output. Three rules cover most of the noise: strip fence language tags and surrounding whitespace, case-fold identifiers used in substring predicates, and sort import lines before line counting. Nothing here judges quality, and that limitation is intentional rather than an oversight.

Patching a checker after seeing a failure is the fastest way to turn a conformance suite into a rubber stamp. The pair's rule was that a predicate change requires a separate commit with its own justification, which makes the temptation visible in review.

The decision table the pair kept

Observation Action
Free tier passes all predicates and the failure set matches the baseline Keep the free tier for drafting and scaffolding
Free tier fails a predicate that the baseline passes Open an issue with the manifest path and the prompt hash
Both backends fail the same predicate The predicate is wrong; fix the case file first
A run cannot be reproduced from its manifest Freeze adoption; the comparison is not evidence
The free allowance or server option changes Re-run the suite before trusting prior results
Any case requires human reading to judge Move it out of the suite and into review

Rows four and six carried the session, because they define exits rather than endorsements. A team that cannot reproduce a run has no basis for a decision, and a case that needs human taste does not belong in an automated gate.

The decision the pair actually kept

The kept decision was scoped to drafting and test scaffolding, with a written boundary that production logic stays under human review regardless of how green the suite looks. The senior's framing was that a passing harness proves consistency across endpoints, not correctness of the code under discussion, and the team adopted that sentence verbatim in the ADR.

The second kept item was the abandon criterion, which named two triggers: repetitive predicate failures on the free tier and any change in availability terms that cannot be re-verified from primary documentation. Writing the exit before the enthusiasm is what made the adoption reversible.

Limitations and who should not use this approach

The harness measures conformance to hand-written predicates and nothing else, so it says little about readability, security posture, or fitness for a domain the predicates do not cover. Manifests also have a shelf life, and a run recorded against an alias that no longer exists is archaeology rather than evidence.

Teams handling data that cannot leave a defined boundary should not route it through any third-party endpoint, free or otherwise. Teams that need bit-for-bit deterministic reproduction should not adopt this at all, since sampling behavior alone breaks that requirement. Anyone who would read a green suite as permission to merge without review has misunderstood the artifact.

A single afternoon with a throwaway copy of this harness is the cheapest way to test whether MonkeyCode's free model access and free server option fit an existing review process, and the manifest files that come out of it are more persuasive to a skeptical senior than any amount of prose.

Top comments (0)