You should port the tool contract before you repoint a coding workflow at free model access. Leftover paid endpoints, unbounded retries, and vendor tool names survive cancellation and keep steering the next run. A portable contract plus a leftover scan gives you a cutover you can rehearse on a laptop. Free server capacity does not repair a tool schema that still calls the old paid host.
This migration diary treats the move as an operations problem, not as a contest between model brands. This is not a ledger of past tool calls, and it is not a snapshot of the public API your service already exposes. You keep review rules that are yours, and you drop bindings that only exist because a vendor SDK hid the endpoint. It is a proposal you should execute in a scratch directory, not a claim that any particular quota will hold.
A paid coding agent usually stores three things that never appear in the chat transcript you export. The first is a tool map that binds a local name to a vendor URL, a timeout, and a retry policy. The second is a permission note that lists repositories, shells, and network hosts the agent may touch. The third is a cache of failed calls that still contains the old host string and the old account id.
If you copy that directory onto a free server, you import the old bill path together with the useful rules. You should separate portable behavior from vendor residue before you delete the paid account for good. Portable behavior includes the file patterns you allow, the commands you forbid, and the diff size you accept. Vendor residue includes base URLs, proprietary tool names, hidden telemetry flags, and retries that assume a generous limit.
Write both lists down while you still have access, because the export button often omits the retry block. A later host cannot reconstruct a timeout you never wrote down, even if the model itself is still reachable. Treat the missing retry block as a cutover defect, not as a detail you will remember next week.
The artifact you want is a decision table, a local scanner, and a short acceptance test you can rerun after every config edit. Read each binding once and mark it with one of four outcomes before you pick a new host. Keep means the rule still describes your repository and does not name a vendor, a host, or an account. Rewrite means the behavior is worth saving, but the endpoint, the tool name, or the timeout is not.
Drop means the call only existed to satisfy a paid feature you will not replace on the next host. Block means the binding can reach secrets, production hosts, or a shell you have not reviewed yet. The table is the cutover plan, and you should not migrate a row until its outcome is explicit. You do not migrate a Block row until a human reviews the command, the path list, and the secret source.
You do not fix a Drop row by pointing it at a free model, because that behavior was never yours to keep. A Rewrite row needs a new name, a new timeout, and a new attempt cap before it may load. Leave the old vendor URL in the residue file so you can prove it was removed. If you cannot classify a row, mark it Block and stop the cutover for that tool.
| Binding clue | Outcome | Why it fails after cutover |
|---|---|---|
| Generic allowlist of paths | Keep | Still describes your repo and names no host |
| Vendor base URL or SDK host | Rewrite | The next process will keep calling the paid API |
| Unlimited retry on HTTP 429 | Rewrite | Free access often throttles sooner than the paid plan |
| Hidden telemetry or account id | Drop | It still reports to the old vendor after you leave |
| Shell with network plus secrets | Block | A new host should not inherit an unreviewed command |
Create a directory that holds only the contract, and keep it outside the repository you intend to edit. Copy the agent config, then reduce it to fields that name the tool, the paths, the timeout, and the attempts. Leave the vendor URL in a side file so the scanner can see it, but do not load that file later.
mkdir -p cutover/tool-contract
cp agent/tools.json cutover/tool-contract/tools.vendor.json
python3 cutover/freeze_contract.py cutover/tool-contract/tools.vendor.json
The freeze script below is an unexecuted proposal, and you should run it only on a copied config. It keeps a small allowlist of fields and writes every other key to a residue file you can review. Do not point the script at a home directory, because unrelated files may contain tokens you did not mean to parse.
#!/usr/bin/env python3
"""Proposal: freeze a portable tool contract. Not executed in this draft."""
import json
import sys
from pathlib import Path
KEEP = ("name", "allowed_paths", "timeout_seconds", "max_attempts", "network")
def freeze(raw):
portable = {}
residue = {}
for tool in raw.get("tools", []):
name = str(tool.get("name", "unnamed"))
portable[name] = {key: tool.get(key) for key in KEEP}
residue[name] = {
key: value for key, value in tool.items() if key not in KEEP
}
return {"tools": portable}, {"residue": residue}
def main():
source = json.loads(Path(sys.argv[1]).read_text())
portable, residue = freeze(source)
out = Path("cutover/tool-contract")
(out / "tools.portable.json").write_text(json.dumps(portable, indent=2))
(out / "tools.residue.json").write_text(json.dumps(residue, indent=2))
print("froze %d tools" % len(portable["tools"]))
if __name__ == "__main__":
main()
After the freeze, open the residue file and expect base URLs, account identifiers, and vendor tool aliases. If that file is empty, you probably exported a summary view rather than the live binding the agent actually loads. Go back to the agent settings and export the raw binding before you cancel the paid subscription.
Paid plans often hide a retry helper that loops until the call succeeds or the vendor timeout gives up. Free model access can be slower, smaller, or simply unavailable on a given day, so you should not assume last month's limit. Set an explicit attempt ceiling in the portable file, and fail the task when that ceiling is hit. Two attempts are a starting proposal, not a measured optimum you should copy into every repository.
If your builds are slow, raise the timeout only for the build tool, and leave ordinary file reads short. A free server does not make a long retry loop cheaper, because you still pay in queue time and log volume. Record the chosen ceiling in the decision table so the next operator does not restore the old unlimited helper.
{
"tools": {
"read_file": {
"name": "read_file",
"allowed_paths": ["src", "tests"],
"timeout_seconds": 20,
"max_attempts": 2,
"network": false
}
}
}
You need a scanner that fails when the portable file still contains a host, a secret-like assignment, or an unbounded attempt count. The command is safe to run in CI because it only reads the contract directory you just created on disk. It does not call a model, and it does not need network access, a token, or a running agent process.
#!/usr/bin/env python3
"""Proposal: fail if a portable contract still carries paid leftovers."""
import json
import re
import sys
from pathlib import Path
PAID_HOST = re.compile(r"https?://\S+", re.I)
SECRET = re.compile(r"(api[_-]?key|bearer|secret|token)\s*[:=]", re.I)
def scan(document):
findings = []
if PAID_HOST.search(document):
findings.append("url still present in portable contract")
if SECRET.search(document):
findings.append("secret-like assignment still present")
data = json.loads(document)
for name, tool in data.get("tools", {}).items():
attempts = tool.get("max_attempts", 1)
if not isinstance(attempts, int) or attempts < 1 or attempts > 3:
findings.append("%s: max_attempts outside 1..3" % name)
if tool.get("network") and not tool.get("allowed_paths"):
findings.append("%s: network enabled without a path allowlist" % name)
return findings
def main():
text = Path(sys.argv[1]).read_text()
findings = scan(text)
if findings:
print("\n".join(findings))
raise SystemExit(1)
print("portable contract passed leftover scan")
if __name__ == "__main__":
main()
python3 cutover/scan_leftovers.py cutover/tool-contract/tools.portable.json
Treat a failure as a blocked cutover, not as a warning you suppress to keep the migration timeline pretty. The scanner is intentionally narrow, so a pass does not prove that the workflow is correct or complete. A pass only proves that this file no longer carries the residue patterns you chose to list in code.
When the scan passes, point a scratch workspace at free model access and run one boring, read-only task. Ask for a summary of the tests directory only, leave network disabled, and compare the trace with the portable file. Keep the paid account alive until those two files agree, because you still need the old settings screen if the export was incomplete.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode matters here only as one place that currently offers free model access and a free server option for that rehearsal. Confirm the live limits, the model list, and the duration on the product page before you depend on them. This draft does not record a quota, a hardware shape, a model name, or a promise that the free option will remain.
A useful rehearsal has four checks, and you should write the observed results next to the contract you froze.
If any check fails, stop and fix the contract before you change models, hosts, or prompt wording. A different model will still call the host you forgot to delete, and a larger context window will not hide that. Write the failing check into the residue review so you do not rediscover it after the paid account is gone.
Cancel the paid account only after the rehearsal log is stored in the same directory as the frozen contract. Export nothing else from the vendor interface unless you have a written retention reason for that specific file. Chat vanity, unused prompt packs, and telemetry identifiers are leftovers, not assets you need on the next host.
Keep the decision table, the portable JSON, the residue review, and the four-check log, and discard the rest. You should also rotate any key that ever lived in the old agent config, even if the scanner did not print it. Config exports are an incomplete picture, and a copied dotfile can still hold a token the JSON summary omitted. Rotation stays a human step, and this diary does not script it, schedule it, or store the new secret for you.
Skip this approach if the agent can deploy, send mail, or touch production data without a separate approval gate. The scanner does not understand your business risk, and a free server is not a sandbox just because the price is zero. Skip it if you cannot export the raw tool bindings, because guessing the schema invents permissions you do not actually have.
Skip it if the workflow depends on a vendor-only tool you have not replaced with a local command you trust. This proposal also assumes a single repository and a single operator who can read every binding before the cutover. A shared agent platform needs owners, an inventory, and a rollback, which this diary does not try to provide. Do not treat the attempt cap as a benchmark, and do not treat a scratch success as proof that free access remains next week.
If you want a low-stakes place to rehearse the portable contract, use free model access and a free server, then keep the log. The log is the part you can carry to the next host when this one changes its terms, its models, or its price. That habit matters more than the host you pick today, because the next cutover will start from the log rather than from memory.