A pairing session with a senior engineer should freeze a rollback contract before any free coding model is allowed to change a shared server. The contract records every question asked, every dead-end hypothesis, and exactly one kept decision about how a patch may land. Without that freeze, free-model access turns a staging host into an unreviewed experiment rather than an engineering change. The workflow below treats the pairing log itself as the merge gate, not the generated patch from any model.
The problem the pairing is meant to catch
Free coding models make it cheap to emit a diff, and a shared staging process makes it cheap to apply that diff. Industry talk about vibe coding often stops at taste, while the operational failure is a host that changed without a reversible plan. A senior pairing partner notices that gap because the partner has been paged for similar silent edits. The pairing therefore refuses to let the model speak to the server until a rollback contract exists on disk.
Where a free model and a free server fit
Teams sometimes reach for MonkeyCode when they need free model access and a free server option for agent trials.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Those two availability facts do not replace review, ownership, or a rollback path on the host that will run the patch. The pairing log described below still applies if every product name is removed from the working method.
Worked example: overlapping POST /jobs
The following session is a labeled worked example, not a claim about a production outage at a named company. A staging service accepts POST /jobs and occasionally drops the last item when two requests overlap. The junior engineer wants a free model to rewrite the handler on the shared host immediately. The senior engineer refuses that path until four questions, two dead ends, and one kept decision are written to pairing_log.json.
1. Freeze the staging file and the failing test
The pairing starts by freezing what staging currently does, not by asking a model for a better handler. A health check and a copy of the running file become the rollback baseline for the rest of the session. The senior engineer will not discuss prompt text until those two artifacts exist beside the repository. The commands below assume a local staging replica that mirrors the shared host layout for this exercise.
mkdir -p app .pairing_frozen/app
python3 - <<'PY'
from pathlib import Path
src = Path("app/jobs.py")
if not src.exists():
raise SystemExit("app/jobs.py missing; stop before any model edit")
Path(".pairing_frozen/app/jobs.py").write_text(src.read_text())
Path(".pairing_frozen/stamp").write_text("frozen\n")
print("froze", src, "->", ".pairing_frozen/app/jobs.py")
PY
python3 test_overlap.py --expect-fail
The buggy handler used in the example is a read-copy-write on a process-wide list, which loses items under overlapping POSTs. The unused lock in the file is deliberate, because it shows how a model can “see” a lock and still patch the wrong region. The pairing freezes this file first so later rollback does not depend on git history being clean.
# app/jobs.py — labeled worked example, currently racy
from http.server import BaseHTTPRequestHandler
import json
import threading
JOBS = []
_lock = threading.Lock() # present, but the handler never takes it
class Handler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
return
def do_POST(self):
length = int(self.headers.get("Content-Length", "0"))
payload = json.loads(self.rfile.read(length) or b"{}")
current = list(JOBS)
current.append(payload)
JOBS[:] = current # overlap can drop an append
body = json.dumps({"stored": len(JOBS)}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
# test_overlap.py — twenty concurrent posts against a threaded server
from __future__ import annotations
import importlib.util
import json
import sys
import threading
import urllib.request
from http.server import ThreadingHTTPServer
from pathlib import Path
def load_jobs():
path = Path(__file__).parent / "app" / "jobs.py"
spec = importlib.util.spec_from_file_location("jobs", path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def main() -> None:
mod = load_jobs()
server = ThreadingHTTPServer(("127.0.0.1", 0), mod.Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
port = server.server_address[1]
errors: list[str] = []
def post(n: int) -> None:
req = urllib.request.Request(
f"http://127.0.0.1:{port}/jobs",
data=json.dumps({"id": n}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=2) as resp:
resp.read()
except Exception as exc: # noqa: BLE001
errors.append(str(exc))
workers = [threading.Thread(target=post, args=(i,)) for i in range(20)]
for worker in workers:
worker.start()
for worker in workers:
worker.join()
server.shutdown()
stored = len(mod.JOBS)
print(f"posted=20 stored={stored} errors={len(errors)}")
lost = stored < 20
expect_fail = "--expect-fail" in sys.argv
if expect_fail and lost:
sys.exit(0)
if expect_fail and not lost:
sys.exit("race did not drop jobs; freeze a failing baseline instead")
if not expect_fail and (lost or errors):
sys.exit("overlap still drops jobs; restore the frozen handler")
sys.exit(0)
if __name__ == "__main__":
main()
2. Store the senior questions as required fields
Spoken chat transcripts vanish quickly, so the senior's questions are stored as required fields in pairing_log.json. Each question has an owner, a one-line answer, and a timestamp so later patches cannot pretend the discussion never happened. The pairing session does not proceed while any answer field is still empty in that log. The four questions used in this worked example are listed with answers after the JSON schema.
{
"session_id": "jobs-overlap-2026-09-19",
"questions": [
{
"asked_by": "senior",
"text": "What is the smallest input that still drops a job?",
"answer": "Twenty concurrent POST /jobs bodies with distinct id fields.",
"answered_at": "2026-09-19T10:12:00Z"
},
{
"asked_by": "senior",
"text": "Which single file is allowed to change in this session?",
"answer": "app/jobs.py only.",
"answered_at": "2026-09-19T10:14:00Z"
},
{
"asked_by": "senior",
"text": "Which command restores the frozen handler if the overlap test still fails?",
"answer": "cp .pairing_frozen/app/jobs.py app/jobs.py",
"answered_at": "2026-09-19T10:16:00Z"
},
{
"asked_by": "senior",
"text": "Who watches the shared staging process for the next hour?",
"answer": "Junior owns the process; senior owns the rollback command.",
"answered_at": "2026-09-19T10:18:00Z"
}
],
"dead_ends": [],
"kept_decision": {}
}
The senior asked about the smallest failing input first, because a full rewrite is usually a way to hide an unproven race. File ownership came second, so the model would not receive a license to touch configuration, tests, and the handler in one prompt. The restore command came third, because a shared server without a known undo path is not a pairing environment. Watch ownership came last, so paging duty would not dissolve into the model transcript after the session ended.
3. Write the dead ends before the next prompt
The first dead end was wrapping the entire handler in a process-wide lock, which hid the race by destroying concurrency. The second dead end was introducing an external queue in the same change, which mixed infrastructure work with a one-line race fix. Both ideas are recorded with a reject reason so a later model prompt cannot revive them quietly. The senior engineer treats a recycled dead end as a failed pairing, not as creative iteration on the same bug.
{
"dead_ends": [
{
"hypothesis": "Hold _lock across the entire do_POST body, including JSON writes.",
"reject_reason": "Serializes every request to hide the race instead of protecting the list swap.",
"rejected_by": "senior"
},
{
"hypothesis": "Replace JOBS with an external queue inside the same patch.",
"reject_reason": "Expands scope from one file race to new infrastructure on the shared host.",
"rejected_by": "senior"
}
]
}
A free model will often rediscover both ideas because they are locally coherent and appear in many training examples. The pairing log makes that rediscovery expensive, because the apply script can refuse a patch whose comments or diff hunks match a rejected hypothesis. The worked example keeps that check human-readable rather than turning it into a classifier. The important part is the written reject reason, not a clever similarity score.
4. Keep one rollback contract
The kept decision is narrow on purpose, so only app/jobs.py may change during this pairing session. The apply tool must restore the frozen copy if the overlap test still fails after the patch. The rollback command is part of the decision, not a comment the model can quietly ignore. That single-decision rule is what keeps a free model from expanding the blast radius on a shared server.
{
"kept_decision": {
"summary": "Take _lock only around the list copy and swap in app/jobs.py.",
"allowed_files": ["app/jobs.py"],
"rollback_command": "cp .pairing_frozen/app/jobs.py app/jobs.py",
"verify_command": "python3 test_overlap.py",
"second_decision": null
}
}
The intended patch is then small enough to review in one screen. The lock already sitting in the file is used only around the copy and assignment, which preserves request concurrency for JSON encoding. No configuration file, Dockerfile, or test rewrite is in scope, even if a model offers them as a bonus. The pairing ends if a second decision appears, including a “while we are here” rename.
5. Enforce the contract before overwrite
A small Python gate reads the log, the frozen snapshot, and the proposed diff before any file is overwritten. The script exits nonzero when questions, dead ends, or the rollback command are missing from the log. It also rejects a patch that touches a path outside allowed_files, which is the mechanical form of the kept decision.
#!/usr/bin/env python3
"""Refuse to apply a patch unless the pairing log holds a rollback contract."""
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent
LOG_PATH = ROOT / "pairing_log.json"
FROZEN_DIR = ROOT / ".pairing_frozen"
RECEIPT_PATH = ROOT / "pairing_receipt.json"
def fail(message: str) -> None:
print(f"pairing-gate: {message}", file=sys.stderr)
raise SystemExit(1)
def load_log() -> dict:
if not LOG_PATH.exists():
fail("pairing_log.json is missing; freeze questions before any patch")
return json.loads(LOG_PATH.read_text())
def require_questions(log: dict) -> None:
questions = log.get("questions") or []
if len(questions) < 4:
fail("senior pairing requires at least four answered questions")
for item in questions:
if not item.get("text") or not item.get("answer") or not item.get("asked_by"):
fail("each question needs text, asked_by, and a nonempty answer")
def require_dead_ends(log: dict) -> None:
dead_ends = log.get("dead_ends") or []
if not dead_ends:
fail("record at least one rejected hypothesis before applying a patch")
for item in dead_ends:
if not item.get("hypothesis") or not item.get("reject_reason"):
fail("each dead end needs a hypothesis and a reject_reason")
def require_decision(log: dict) -> None:
decision = log.get("kept_decision") or {}
if not decision.get("summary"):
fail("kept_decision.summary is missing")
if not decision.get("allowed_files"):
fail("kept_decision.allowed_files must list the only paths a patch may touch")
if not decision.get("rollback_command"):
fail("kept_decision.rollback_command is the contract; do not apply without it")
if decision.get("second_decision"):
fail("this pairing allows one kept decision only")
def main(argv: list[str]) -> None:
if len(argv) < 2:
fail("usage: pairing_gate.py <file> [<file> ...]")
log = load_log()
require_questions(log)
require_dead_ends(log)
require_decision(log)
allowed = set(log["kept_decision"]["allowed_files"])
extra = [path for path in argv[1:] if path not in allowed]
if extra:
fail(f"patch touches files outside the kept decision: {extra}")
for path in argv[1:]:
frozen = FROZEN_DIR / path
if not frozen.exists():
fail(f"no frozen snapshot for {path}; freeze staging before the model runs")
receipt = {
"kept_decision": log["kept_decision"]["summary"],
"files": argv[1:],
"rollback_command": log["kept_decision"]["rollback_command"],
}
RECEIPT_PATH.write_text(json.dumps(receipt, indent=2) + "\n")
print("pairing-gate: contract complete; operator may apply the listed files")
if __name__ == "__main__":
main(sys.argv)
6. Apply, verify, or restore
After the gate passes, the overlap test runs against the staging replica before anyone celebrates the model output. Failure restores the frozen file and appends a new dead-end record instead of leaving the host half-patched. Success writes a short decision receipt so the next pairing session cannot claim the change was unsigned. The receipt is ordinary JSON, which later reviews can grep without opening a chat export from the model.
python3 pairing_gate.py app/jobs.py
# operator applies the one allowed file only after the gate prints success
python3 test_overlap.py || cp .pairing_frozen/app/jobs.py app/jobs.py
The patched region that matches the kept decision is the lock around the list swap, not a new framework. The rest of the handler, including status codes and logging behavior, stays frozen because it was never in the contract. If the overlap test still loses jobs, the copy command in the log is the entire incident response for this session. The pairing does not open a second prompt to “just try another idea” on the live host.
# kept patch inside do_POST — still a labeled example, not a benchmark
with _lock:
current = list(JOBS)
current.append(payload)
JOBS[:] = current
Stop rules
A pairing session still needs a stop rule when the senior and the model disagree about scope. The markdown table below is the stop rule used in this worked example for staging patches. Rows are evaluated in order, and the first matching row wins without extra debate in chat. The gate script encodes the same rows so a tired operator cannot skip them under time pressure.
| Condition | Pairing action | Server action |
|---|---|---|
| pairing_log.json missing an answered question | Stop the session | Leave staging untouched |
| Proposed files outside allowed_files | Reject the patch as a second decision | Leave staging untouched |
| Overlap test still loses jobs after apply | Append a dead-end record | Run rollback_command |
| Overlap test passes and receipt is written | Close the session | Keep the single-file change |
Limitations
This workflow assumes two humans can sit with the same repository and a writable pairing_log.json file. It does not measure model quality, token burn, or server capacity, and it invents none of those numbers. A solo developer can still keep the log, but the senior questions then become self-review prompts with weaker resistance. Shared hosts that cannot snapshot a single file should not use the apply script at all.
The overlap test is a teaching probe for one race, not a load test and not a proof of thread safety under every runtime. ThreadingHTTPServer on localhost will not reproduce every failure mode of a remote free server under real traffic. The gate also cannot see side effects outside the listed files, such as crontab edits or manual package installs. Those gaps are why the kept decision stays small enough to undo with one copy command.
Who should skip this method
Teams with a real deployment pipeline, signed commits, and automatic rollback should not replace that pipeline with this pairing gate. The method is a teaching brake for agent trials on disposable staging, including a free server used for experiments. It is the wrong tool for production traffic, regulated data, or any host that other customers already share. It is also the wrong tool when nobody in the pairing can answer the four questions without guessing.
What the pairing actually kept
Engineering remains the pairing, the frozen baseline, and the one kept rollback contract written before the patch. Free model access only changes how fast a rejected idea can reappear, which is why dead ends must be files. Readers who already run agent trials on a shared host can copy the gate script and keep their current models. A useful next step is to run this freeze once on the next agent patch, then stop if the log cannot be filled honestly.
Top comments (0)