A pairing session should freeze retry methods and an idempotency key rule before any generated client code is allowed to ship. Free coding models write retry loops quickly, and those loops often treat every HTTP method as safe to repeat. The cost appears later as duplicate charges, repeated side effects, and logs that cannot name a canonical attempt. This article records one senior pairing as a transcript, then turns the kept decision into a checker.
The session below is a labeled worked example for a local HTTP helper, not a report of a named production outage. The pairing is completed before a model is asked to implement retries, timeouts, or backoff curves for the client. The artifact is a YAML transcript, a Python checker, a constrained client, and a small invariant test file. Teams can run the checker in CI without adopting any particular coding assistant or remote development host.
The problem the pairing had to stop
Internal tools often grow a retry wrapper after the first timeout appears in shared logs. A model can emit exponential backoff, jitter, and status-code lists in a single pass that looks complete. That output still leaves POST and PATCH inside the retry set, which is the failure mode the senior was asked to block. Review that only checks latency and HTTP status will bless the wrapper while remaining silent about duplicate side effects.
Generated tests are getting easier to produce, and that ease is part of the trap rather than the fix. A suite can prove that a call eventually returns 200 and still never ask whether the server ran a charge twice. The pairing therefore spends its time on method semantics and key identity, not on polishing sleep curves. The transcript is the review object; the later patch is only allowed to fill the envelope the transcript already closed.
Step 1: write the senior questions into a file
The pairing opens with a short question list that the senior expects answered in writing. Each answer names an owner file and a constraint, not a feeling about reliability or user impact. The worked example uses four questions only, so the session cannot wander into unrelated refactors or framework rewrites. Chat history is not the source of truth once the YAML exists on the branch.
# pairing-session.yaml
session_id: retry-helper-2026-09-20
status: kept
questions:
- id: Q1
ask: Which HTTP methods may the helper retry without creating duplicate side effects?
answer: Only GET, HEAD, and PUT appear on the allowlist. POST and PATCH stay out.
owner_file: retry_policy.yaml
- id: Q2
ask: What header makes two attempts the same logical request when a method is retried?
answer: Idempotency-Key must be present, and the prefix must stay inv-2026-09-.
owner_file: retry_policy.yaml
- id: Q3
ask: May generated development traffic leave the workstation for a remote coding host?
answer: Development completion may use a remote coding host. Runtime product traffic may not.
owner_file: egress_policy.yaml
- id: Q4
ask: Where do secrets for the product API live during generation and during tests?
answer: Secrets stay in local env files that the generator cannot write. Tests use fixtures.
owner_file: secrets.policy
Those four answers already bound the patch surface for anyone who reads the pull request later. A model prompt can receive the YAML as context, but reviewers still read the file rather than a screenshot of a conversation. If a later patch changes methods or the key prefix, the pairing status is no longer kept and the session must be reopened on purpose.
Step 2: publish the dead ends before anyone codes
Dead ends are rejected designs with a reason that a checker or a reviewer can quote without a meeting. The pairing does not keep a polite maybe for a future cleanup pull request. Each dead end blocks a tempting patch that generated code produces often when the prompt says make it more resilient. Writing them down is cheaper than arguing the same three designs after the helper already merged.
- Retry every 5xx response for every method, because POST charges and webhook delivery would repeat under load.
- Add exponential backoff without a method allowlist, because delay does not make a non-idempotent call safe to replay.
- Let the client mint raw UUIDs as keys, because collisions across services would not share a frozen namespace.
- Route runtime API calls through the same host used for code generation, because development egress is not an app dependency.
dead_ends:
- id: D1
rejected: retry_all_5xx
reason: Status-code retries ignore method semantics and duplicate POST side effects.
- id: D2
rejected: backoff_without_allowlist
reason: Timing policy cannot substitute for an explicit method allowlist.
- id: D3
rejected: unprefixed_uuid_keys
reason: Keys without the frozen prefix cannot be grepped or revoked as a family.
- id: D4
rejected: runtime_via_codegen_host
reason: A coding host is not an approved production hop for product HTTP.
When a later patch revives retry_all_5xx, the reviewer can point at D1 instead of reconstructing the argument from memory. The implementation step stays narrow because the pairing already spent its attention on the rejected shapes. That is the whole point of recording dead ends in the same commit as the helper.
Step 3: keep one decision and refuse to reopen it in the patch
The pairing keeps a single decision that later patches must not reopen while claiming to be a small cleanup. Extra improvements, including smarter jitter and hedged requests, wait for a new session with a new identifier. The kept decision in this example is narrow on purpose so a lexical checker can enforce it. Broad reliability slogans are not decisions.
Kept decision: the helper may retry only allowlisted methods, and every retried attempt must send Idempotency-Key values that start with inv-2026-09-.
kept_decision:
id: KD-retry-allowlist
summary: Retry GET, HEAD, and PUT only, with a frozen Idempotency-Key prefix.
allow_methods: [GET, HEAD, PUT]
deny_methods: [POST, PATCH, DELETE]
key_header: Idempotency-Key
key_prefix: inv-2026-09-
max_attempts: 3
reopen: false
The same rule can be read as a table during review, which is useful when the YAML is long and the patch is not.
| Method | Retry allowed | Pairing reason |
|---|---|---|
| GET | yes | Read path in this service has no billed side effect |
| HEAD | yes | Metadata read is treated as idempotent |
| PUT | yes | Same body and key must replace one resource |
| POST | no | Creates charges, mail, or webhook work |
| PATCH | no | Partial updates are not frozen as idempotent here |
| DELETE | no | Replay can remove a resource the caller already replaced |
Generated code likes to reopen policy while renaming functions and extracting a shared transport layer. The reopen: false flag tells both humans and tools that a new pairing is required to change methods or the prefix. Without that flag, the transcript becomes commentary and the next model run wins.
Step 4: fail CI when the patch violates the kept decision
A transcript that cannot fail CI will lose to the next generated patch, usually before lunch. The checker below is a labeled example for this tree, not production-hardened policy software. Operators should run it against their own filenames and extend the regexes if the client uses helpers. The senior accepted a cheap gate that catches the common generated pattern, not a soundness proof of every call path.
# check_kept_decision.py
from __future__ import annotations
import re
import sys
from pathlib import Path
import yaml
ALLOWED = {"GET", "HEAD", "PUT"}
DENIED = {"POST", "PATCH", "DELETE"}
PREFIX = "inv-2026-09-"
KEY_HEADER = "Idempotency-Key"
RETRY_CALL = re.compile(
r"retry_request\(\s*method\s*=\s*[\"'](\w+)[\"']",
re.MULTILINE,
)
KEY_LITERAL = re.compile(
r"[\"']Idempotency-Key[\"']\s*:\s*f?[\"']([^\"']+)[\"']"
)
def load_decision(path: Path) -> dict:
data = yaml.safe_load(path.read_text())
decision = data["kept_decision"]
if decision.get("reopen") is True:
raise SystemExit("kept decision was reopened without a new pairing session")
return decision
def scan_source(root: Path) -> list[str]:
failures: list[str] = []
for path in root.glob("*.py"):
if path.name.startswith("check_"):
continue
text = path.read_text()
for match in RETRY_CALL.finditer(text):
method = match.group(1).upper()
if method in DENIED or method not in ALLOWED:
failures.append(
f"{path}: retry on {method} violates KD-retry-allowlist"
)
for match in KEY_LITERAL.finditer(text):
value = match.group(1)
if not value.startswith(PREFIX) and PREFIX not in value:
failures.append(f"{path}: idempotency key missing prefix {PREFIX}")
if "retry_request(" in text and KEY_HEADER not in text:
failures.append(f"{path}: retry_request used without {KEY_HEADER}")
return failures
def main() -> int:
decision = load_decision(Path("pairing-session.yaml"))
if set(decision["allow_methods"]) != ALLOWED:
print("pairing allow_methods drifted from checker constants", file=sys.stderr)
return 1
failures = scan_source(Path("."))
for item in failures:
print(item, file=sys.stderr)
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())
The checker is lexical on purpose, and that limitation belongs in the pairing notes rather than in a later excuse. It will miss getattr tricks, dynamic method names, and retries written in another language. Catching the default shape that models emit is still worth a few dozen lines, because that is the shape that otherwise merges.
Step 5: implement only inside the frozen envelope
After the transcript and checker exist, implementation can proceed without reopening method policy. The client below follows the kept decision and refuses denied methods before any network call is attempted. Comments and names label this as unexecuted sample code for the article. Production services still need a server that honors the same key.
# retry_client.py
from __future__ import annotations
from dataclasses import dataclass
ALLOWED_METHODS = frozenset({"GET", "HEAD", "PUT"})
KEY_PREFIX = "inv-2026-09-"
@dataclass(frozen=True)
class RetryEnvelope:
method: str
url: str
idempotency_key: str
attempts: int = 3
class PolicyError(ValueError):
pass
def validate_envelope(env: RetryEnvelope) -> None:
method = env.method.upper()
if method not in ALLOWED_METHODS:
raise PolicyError(f"{method} is outside the pairing allowlist")
if not env.idempotency_key.startswith(KEY_PREFIX):
raise PolicyError("idempotency key missing frozen prefix")
if env.attempts > 3:
raise PolicyError("attempt budget exceeds kept decision")
def retry_request(method: str, url: str, key: str, send):
env = RetryEnvelope(method=method, url=url, idempotency_key=key)
validate_envelope(env)
headers = {"Idempotency-Key": env.idempotency_key}
last_error = None
for _ in range(env.attempts):
try:
return send(env.method, env.url, headers)
except TimeoutError as exc:
last_error = exc
assert last_error is not None
raise last_error
A small test file records the pairing as executable examples instead of a narrative in the pull request. These tests are the reviewer's fast path when a model sends a larger transport refactor. They do not prove exactly-once behavior on the server; they prove the client still refuses the dead ends.
# test_retry_invariants.py
import pytest
from retry_client import PolicyError, retry_request
def test_post_is_rejected():
with pytest.raises(PolicyError):
retry_request(
"POST",
"https://api.example.internal/charges",
"inv-2026-09-a",
lambda *args: None,
)
def test_get_requires_prefix():
with pytest.raises(PolicyError):
retry_request(
"GET",
"https://api.example.internal/items/1",
"other-a",
lambda *args: None,
)
def test_get_with_prefix_calls_send():
seen = {}
def send(method, url, headers):
seen["method"] = method
seen["headers"] = headers
return 200
status = retry_request(
"GET",
"https://api.example.internal/items/1",
"inv-2026-09-a",
send,
)
assert status == 200
assert seen["headers"]["Idempotency-Key"].startswith("inv-2026-09-")
Local commands stay ordinary and do not require a vendor dashboard for the policy gate.
python -m pytest test_retry_invariants.py
python check_kept_decision.py
Where free model access belongs after the freeze
Generated patches arrive faster than policy arguments, which is why the transcript is committed before any implementation branch is opened. After that freeze, a coding assistant can draft the helper against the YAML without reopening the method allowlist. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source project that, per this account's operator, offers free model access and a free server option for development drafting.
Those options apply to drafting helpers like this retry client, not to production request paths or secret storage. Dead end D4 still forbids using a coding host as a runtime hop for product HTTP traffic. If a team wants a drafting host for that implementation step, MonkeyCode's free model access and free server option can host the session while the YAML and checker stay in review.
The same transcript works if a human intern writes retry_client.py by hand. Removing the product sentence does not remove the pairing, the table, or the tests. That is the test for whether the workflow is real engineering work rather than a story about tools.
Limitations
The lexical checker will miss retries hidden behind getattr, wrappers in other languages, or method names built from configuration at runtime. The frozen prefix is a review aid, not a cryptographic guarantee of exactly-once processing across services. The server on the other side must still honor the key, and this article does not implement server-side deduplication or an outbox.
No duration, quota, hardware, or benchmark is claimed for any coding host in this writeup. A free development server is not described as a production SLA, and it should not be treated as one in capacity planning. Teams that need those numbers should measure their own workloads against primary sources they control.
Who should not use this approach
Skip the workflow when the client already sits behind a queue that deduplicates by business key. Skip it when legal review forbids sending source to a remote coding host, even for drafting. Skip it when every service mints keys differently and no prefix can be frozen without a platform change.
Payment, webhook, and mail senders should not treat this helper as a substitute for an outbox or a broker with replay control. Teams without a senior who can answer the four questions should not ship a generated retry wrapper and call the pairing complete. In those cases the honest kept decision is to refuse generated retries until the ownership model exists.
Top comments (0)