Messy modules do not need a grand rewrite. They need a lock on observed inputs and outputs. Extract one seam only after that lock stays green.
Coding agents often treat current output as accidental noise. Brownfield branches encode money, retries, and odd quirks. Those quirks are the real product contract today.
Cheap generated diffs raise coupling, not clarity. One unbounded patch can break ten silent callers. A trace file is smaller than a redesign note. It is also executable.
Rank heat before you record
Do not start from architecture opinions or layer diagrams. Start from calls that already happen in smoke tests. Rank functions by inbound references times runtime hits. Touch only the top rank.
Follow this order. Skip a step and the later extract lies.
- List candidate modules with a boring import scan.
- Count inbound references for each public function.
- Pair that count with runtime samples when logs exist.
- Pick one function. Never pick a whole package.
# Proposal commands. Not a published benchmark.
rg -n "from ledger import |import ledger" --type py | wc -l
rg -n "def settle_batch\(" --type py
Stop if two functions look equally hot. Split the ranking work first. Dual targets destroy the later seam. Dual targets also poison the lockfile.
Wrap one function with a recorder
The recorder is the artifact. It serializes arguments, kwargs, and results. It appends JSONL. It must not change return values.
This listing is a worked template. It is not production telemetry. Label it as unexecuted until you run it locally.
# recorder.py — characterization wrapper (worked example)
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from typing import Any, Callable
LOCK = Path("behavior.lock.jsonl")
def _dump(value: Any) -> Any:
if isinstance(value, (str, int, float, bool)) or value is None:
return value
if isinstance(value, (list, tuple)):
return [_dump(item) for item in value]
if isinstance(value, dict):
return {str(key): _dump(item) for key, item in sorted(value.items())}
return {"repr": repr(value), "type": type(value).__name__}
def characterize(fn: Callable) -> Callable:
def wrapped(*args: Any, **kwargs: Any):
result = fn(*args, **kwargs)
record = {
"fn": fn.__qualname__,
"args": _dump(args),
"kwargs": _dump(kwargs),
"result": _dump(result),
}
payload = json.dumps(record, sort_keys=True)
record["sha256"] = hashlib.sha256(payload.encode()).hexdigest()
with LOCK.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(record, sort_keys=True) + "\n")
return result
wrapped.__name__ = fn.__name__
wrapped.__qualname__ = fn.__qualname__
return wrapped
Wrap only the ranked function. Leave neighbors untouched.
# ledger.py — messy target (illustrative)
from recorder import characterize
@characterize
def settle_batch(rows: list[dict], cutoff_days: int = 30) -> dict:
kept = [row for row in rows if int(row.get("age", 0)) <= cutoff_days]
total = sum(int(row.get("cents", 0)) for row in kept)
flags = {row["id"] for row in kept if row.get("retry")}
return {"count": len(kept), "cents": total, "retries": sorted(flags)}
Run existing tests or a thin script next. Do not invent new fixtures yet. You want production-shaped rows. Synthetic rows hide the odd branches.
pytest tests/test_ledger_smoke.py -q
wc -l behavior.lock.jsonl
Cap the file after the smoke path finishes. Duplicate traces waste review time. Hash de-duplication can wait one iteration. Redact secrets before the wrapper opens.
# Fail closed if a token-shaped field appears.
rg -n "api_key|password|secret|token" behavior.lock.jsonl && exit 1
Promote traces to closed tests
Each JSONL line becomes one assertion. Missing keys fail. Extra keys fail. List order is part of the contract. Document any allowed sort before you change it.
# test_characterize_settle_batch.py
from __future__ import annotations
import json
from pathlib import Path
from ledger import settle_batch
LOCK = Path("behavior.lock.jsonl")
def _cases():
for line in LOCK.read_text(encoding="utf-8").splitlines():
rec = json.loads(line)
if rec.get("fn") == "settle_batch":
yield rec
def test_settle_batch_matches_lockfile():
assert LOCK.exists(), "record the hot path before extracting"
seen = 0
for rec in _cases():
out = settle_batch(*rec["args"], **rec["kwargs"])
assert out == rec["result"]
seen += 1
assert seen >= 3, "need at least three distinct traces"
Three traces is a floor, not a study. Add more when branches disagree. Do not average results. Characterization is exact replay. Commit the lockfile with the replay test together.
Read a mismatch before you "fix" it
A red test is data. It is not a license to edit goldens. Use this debug order.
- Print the failing
sha256and the function name. - Diff only
resultkeys. Ignore wrapper metadata. - Check argument coercion, especially
int()on strings. - Revert the extract if the public dict shifted.
- Patch the lockfile last, and only for intended product changes.
pytest tests/test_characterize_settle_batch.py -q
python -c "import json; print(json.loads(open('behavior.lock.jsonl').readline())['sha256'])"
Agents like to soothe the assertion. Reject that patch. The lockfile is the caller. The helper is the suspect.
Extract one seam, nothing else
Keep the public function signature frozen. Move one private helper. Re-run the lockfile tests. If they fail, revert. Do not reshape the test to match the helper.
def _keep(rows: list[dict], cutoff_days: int) -> list[dict]:
return [row for row in rows if int(row.get("age", 0)) <= cutoff_days]
def settle_batch(rows: list[dict], cutoff_days: int = 30) -> dict:
kept = _keep(rows, cutoff_days)
total = sum(int(row.get("cents", 0)) for row in kept)
flags = {row["id"] for row in kept if row.get("retry")}
return {"count": len(kept), "cents": total, "retries": sorted(flags)}
That is the whole refactor. No rename campaign. No new package. One helper whose name matches one verb. Stop if readability does not improve.
Decision table
| Signal | Action | Stop condition |
|---|---|---|
| Observed traces under three | Record more smoke paths | Still under three after one full run |
| Traces disagree on key names | Store both shapes as cases | Do not merge schemas |
| Extract changes a public return | Revert the extract | Never patch the lockfile first |
| Function talks to network or clock | Wrap those seams first | Do not record live time |
| Agent diff touches two files | Reject the diff | One seam, one file |
| Arguments contain secrets | Redact or abort | Lockfile must stay non-sensitive |
Where a free coding host fits
Drafting the wrapper is boilerplate. Running the suite should not depend on a dirty laptop. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option. Use the models to draft recorder.py and the replay test. Use the free server option to run pytest against the lockfile. Keep the lockfile in git. Generated text is not the contract. The JSONL file is.
Do not ask any model to invent traces. Invented traces encode a prior, not your repo. Feed the ranked function only. Ask for the wrapper. Then you record. Then you extract.
Limitations
This method preserves behavior. It does not improve behavior. If the hot path is wrong, characterization gold-plates the bug. Do not treat the lockfile as a product spec.
JSON serialization drops object identity. Live sockets and ORM instances need adapters. Without adapters the lockfile stores repr strings. Those strings are brittle across versions.
Nondeterministic code will flap. Seeds, clocks, and network calls need seams first. If you cannot seam them, stop. A flaky lockfile is worse than no tests.
Who should not use this
Skip this workflow on greenfield modules with real unit tests. Skip it when the goal is a behavior change. Skip it when no smoke path exists. Skip it for security-critical parsers that need property tests.
Teams chasing a full rewrite will hate the pace. The point is the pace. One seam per green suite. The lockfile is the review artifact. The helper is optional.
Checklist
- Rank one function by fan-in and runtime heat.
- Wrap it with the JSONL recorder.
- Run existing smoke paths until three traces exist.
- Commit
behavior.lock.jsonlwith the replay test. - Extract one private helper in the same file.
- Re-run. Revert on mismatch. Do not soothe the goldens.
If the extract does not pay for itself in reading time, keep the lock and stop. The next agent diff now has a closed oracle. That is the whole point.
Top comments (0)