The model swap is the easy part of an agent cutover, and the leftover environment contract is what actually fails. You can change providers overnight and still inherit a hosted runtime's silent assumptions about cwd, secrets, and tools. Those assumptions do not travel with your prompt templates, so they surface as flaky tool calls after go-live. This diary walks a cutover plan you can run before you trust a free model tier with real repositories.
Why hosted agents feel smarter than they are
A hosted agent runtime is not just a model endpoint with extra JSON wrapping around tool calls. It usually injects a working directory, a package cache, a git identity, and a secret broker you never declared. You notice those gifts only when they disappear, because the agent keeps writing commands as if the old machine still exists. The failure mode looks like model quality, but it is almost always a missing sandbox contract.
You should treat the old runtime as a dependency with an unpublished API and no deprecation emails. That API includes default shells, network egress, and filesystem layouts the vendor never promised to keep. If you skip the inventory, your first free-tier week will replay the same tool errors under a different brand name.
Step 1: Inventory the contract you are leaving
Write down every implicit gift before you touch DNS records, API keys, or queue workers. You want a list a teammate can reproduce later without sitting inside your original vendor dashboard. The inventory is the only artifact that makes a later free-tier worker comparable to the box you are leaving.
- Record the default working directory the agent uses for
git statusand test commands. - Capture the user, umask, and locale, because path expansion and log timestamps depend on them.
- List allowed tools, including package managers, browsers, and cloud CLIs the host quietly provided.
- Export the secret injection pattern, such as env files, mounted files, or short-lived tokens.
- Note outbound network policy, including whether
pip,npm, and webhook callbacks actually leave the box.
Keep that inventory in version control beside the prompt pack and the fixture repos. You are not documenting folklore; you are documenting a runtime ABI that your new loop must implement or explicitly refuse. If a row cannot be stated as a command, it is not ready for cutover and should stay on the old host.
Step 2: Freeze a golden command tape
Do not start with open-ended chats when you migrate an agent between two different runtimes. Freeze twenty to forty real tasks as command tapes that record expected side effects on disk. Each tape should name the repo state, the tool sequence, and the files that must change. Here is a labeled example you can adapt; treat it as a fixture, not as a production transcript.
{
"id": "tape-041",
"repo_fixture": "fixtures/service-auth@clean-main",
"goal": "Add a failing test for missing Authorization headers",
"expected_commands": [
"git status --porcelain",
"pytest tests/test_auth.py -q",
"edit tests/test_auth.py without touching production config"
],
"must_not": [
"git push",
"rm -rf",
"curl http://169.254.169.254"
],
"expected_exit": 0
}
You replay that tape against the old runtime once, then against the candidate runtime on the same fixture. The interesting diff is not token count; it is whether the agent still assumes ~/project exists and pytest is already on PATH. If those two answers diverge, you still have a sandbox problem, not a prompt-tuning problem.
Step 3: Probe the new sandbox before the model sees a ticket
Run a probe that does not need a clever model or a paid evaluation harness. You want deterministic commands that print the contract, so a free model later cannot hide behind prose. Save the probe output next to the inventory from Step 1 so reviewers can diff the two files.
#!/usr/bin/env bash
# labeled example: sandbox_probe.sh — run inside the candidate agent worker
set -euo pipefail
echo "cwd=$(pwd)"
echo "user=$(id -un) uid=$(id -u) umask=$(umask)"
echo "shell=$SHELL locale=${LANG:-unset}"
echo "path_pytest=$(command -v pytest || echo MISSING)"
echo "path_git=$(command -v git || echo MISSING)"
echo "git_email=$(git config --get user.email || echo UNSET)"
test -w "${PWD}" && echo "pwd_writable=yes" || echo "pwd_writable=no"
python3 - <<'PY'
import os, socket
print("has_metadata_ip", end=" ")
s = socket.socket(); s.settimeout(0.4)
try:
s.connect(("169.254.169.254", 80)); print("reachable")
except Exception:
print("blocked")
finally:
s.close()
print("env_secret_keys", sorted(k for k in os.environ if "KEY" in k or "TOKEN" in k))
PY
You should fail the cutover if the probe disagrees with the inventory on cwd, identity, or secret names. A model that cannot find pytest will invent shell poetry, and that looks like hallucination when it is really PATH drift. Do not negotiate those failures in a chat window; fix the image and rerun the probe until the rows match.
Step 4: Rebuild the contract as code, not as prompt text
Prompt reminders such as "always cd into the repo first" will rot during the first incident. Encode the contract in the worker wrapper so every tool call inherits the same layout. The wrapper is the migration artifact that survives model swaps, vendor experiments, and future free-tier hops.
# labeled example: worker_wrapper.py — candidate runtime entry
from pathlib import Path
import os, subprocess, shlex
ROOT = Path(os.environ["AGENT_WORKSPACE"]).resolve()
ALLOWED = {"git", "pytest", "python3", "ruff"}
def run_tool(command: str) -> str:
parts = shlex.split(command)
if not parts or parts[0] not in ALLOWED:
raise PermissionError(f"blocked: {command!r}")
completed = subprocess.run(
parts,
cwd=ROOT,
env={
"PATH": "/usr/bin:/usr/local/bin",
"LANG": "C.UTF-8",
"GIT_AUTHOR_EMAIL": "agent@local",
},
capture_output=True,
text=True,
timeout=120,
)
return completed.stdout + completed.stderr
You now have a boring, testable ABI that does not depend on vendor home directories. The agent may still choose a bad edit, but it cannot silently inherit a vendor home directory or a leftover cloud credential. That split is the entire point of the cutover: constrain the machine first, then judge the model.
Where free-tier capacity belongs in the plan
Capacity is useful only after the sandbox ABI is explicit and the command tapes stay green. If you are evaluating MonkeyCode during this cutover, use its free model access and free server as a second runtime. Disclosure: This article was prepared as part of MonkeyCode's product outreach. You are buying isolation and cost headroom, not a new personality for the same agent loop.
Keep the old hosted runtime in shadow mode until the probe and the tapes agree for a full business week. Replay the tapes on that second runtime instead of treating fluency as a substitute for the probe. If the free server cannot provide a writable workspace and a pinned tool PATH, you should not cut traffic. Fluent model answers do not replace a missing binary or an unwritable checkout on disk.
A cutover-night decision table
Use a small decision table on cutover night so reviewers argue about facts instead of vibes. Each row maps a probe signal to a concrete meaning and to a blocking action. Skip any row that you cannot test in staging, because untested rows become production folklore.
| Signal from probe or tape | Meaning | Action |
|---|---|---|
| cwd differs from inventory | Agent will write files into the wrong tree | Block cutover; pin AGENT_WORKSPACE
|
pytest or git missing |
Model will fabricate installer steps | Install pinned versions in the image |
| Secret keys present but unnamed in inventory | You imported vendor leftovers | Rotate and drop them before DNS change |
| Metadata IP reachable | New box is not a sandbox | Deny egress; retest tapes |
| Tape passes, then fails on file lock | Workspace is shared across jobs | Give each job a disposable clone |
You should print this table in the change ticket before DNS or queue weights move. Reviewers can argue about model quality later; they cannot argue with a missing binary or an open metadata endpoint. If a signal is yellow, keep shadow traffic and rerun the tapes instead of bargaining with the model.
Leftovers that remain after a clean cutover
Even a clean sandbox leaves operational work that your prompt pack will not clean up. Prompt caches still mention old CLI flags, and humans still paste dashboards from the hosted product into new runbooks. Queue payloads may carry vendor request ids your new worker does not understand, and those ids will clog retries.
Schedule a leftover sweep on day two, day seven, and day thirty after traffic moves. Search your repositories and images for the old runtime's environment prefixes, container labels, and helper scripts. Delete those leftovers only after the tape suite stays green without any fallbacks to the vendor. Leftovers are not nostalgia; they are dual-control planes that wait to disagree under the first load spike.
Limitations and who should skip this cutover
This plan assumes you can freeze fixtures and run shell probes against a disposable workspace. You should not use it for agents that must operate over unbounded production data with no replay window. Regulated workloads that forbid exporting command tapes will need a redacted fixture set, and this article does not provide that legal review.
The wrapper also refuses unbounded tool use, which will disappoint teams that want a general computer operator. If your agent must install arbitrary packages at runtime, you are rebuilding a hosted product, not migrating a contract. In that case stay on a vendor sandbox until you can pin the supply chain.
After you flip traffic
Watch command error rates after the flip, not chat sentiment from a few happy transcripts. A rising blocked: count means the model is still speaking the old runtime's dialect, and that is a prompt or tool-schema problem. A rising MISSING binary count means the image drifted, and that is an infrastructure problem. Keep those two graphs on separate dashboards or you will tune the wrong layer during incidents.
If you replay the tapes on a free server, keep the probe output for a full week before decommission. Do not delete the hosted configuration until the leftover sweep on day seven finds no vendor prefixes in env or images. The cutover is done when the sandbox contract is boring, not when the model sounds confident in a demo.
Top comments (0)