Local codegen still beats most remote hops on wall clock. A free server only wins after the laptop queue stalls. Printed token banners do not decide that race.
The rest of this article is a timing method. It treats the network as a cost, not a default. Offline remains the honest baseline for short edits.
Think of a bicycle versus a freight train. One page does not need steel rails. You board the train only when the street is jammed.
Public AI threads this week skip that street. They argue models already outcode most working developers. They rarely clock the path those tokens travel.
Vibe-coded hops hide delay inside chat chrome. Engineering writes a budget before the first paste. The tool is a clock, not a mood.
Keep the prompt on disk at the start. Keep the repo map and the secrets there too. Send bytes only after local wait crosses a written limit.
That limit is milliseconds you can defend later. It is not taste and not a slogan. Write it down before any remote call.
The harness below is an unexecuted example. Run it on your machine only. Do not treat the constants as measured truth.
#!/usr/bin/env python3
"""local_vs_hop.py — compare local wait against a remote RTT budget.
Unexecuted example. Fill LOCAL_CMD and REMOTE_URL for your setup.
The probe never ships source, secrets, or prompts.
"""
from __future__ import annotations
import argparse
import json
import statistics
import subprocess
import time
import urllib.error
import urllib.request
from pathlib import Path
DEFAULT_BUDGET_MS = 450.0 # written limit, not a published benchmark
HOP_PAD_MS = 80.0 # starting jitter pad; recalibrate on your route
def time_local(cmd: list[str], rounds: int) -> list[float]:
samples: list[float] = []
for _ in range(rounds):
t0 = time.perf_counter()
subprocess.run(cmd, check=False, capture_output=True)
samples.append((time.perf_counter() - t0) * 1000.0)
return samples
def time_hop(url: str, rounds: int, timeout: float) -> list[float]:
samples: list[float] = []
payload = b'{"ping":true}' # health probe, not repository text
for _ in range(rounds):
req = urllib.request.Request(
url,
data=payload,
method="POST",
headers={"Content-Type": "application/json"},
)
t0 = time.perf_counter()
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
resp.read()
samples.append((time.perf_counter() - t0) * 1000.0)
except (urllib.error.URLError, TimeoutError):
samples.append(float("inf"))
return samples
def decide(local_ms: float, hop_ms: float, budget_ms: float) -> str:
if hop_ms == float("inf"):
return "offline: keep work on disk"
if local_ms <= budget_ms:
return "local: laptop still inside the budget"
if hop_ms + HOP_PAD_MS < local_ms:
return "hop: remote queue is materially faster"
return "local: hop does not pay the trip"
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--local-cmd", nargs="+", default=["python3", "-c", "pass"])
parser.add_argument("--remote-url", default="")
parser.add_argument("--rounds", type=int, default=7)
parser.add_argument("--budget-ms", type=float, default=DEFAULT_BUDGET_MS)
parser.add_argument("--out", type=Path, default=Path("hop_budget.json"))
args = parser.parse_args()
local = time_local(args.local_cmd, args.rounds)
hop = (
time_hop(args.remote_url, args.rounds, timeout=2.0)
if args.remote_url
else [float("inf")] * args.rounds
)
finite_hop = [x for x in hop if x != float("inf")]
hop_p50 = statistics.median(finite_hop) if finite_hop else float("inf")
report = {
"local_p50_ms": statistics.median(local),
"hop_p50_ms": None if hop_p50 == float("inf") else hop_p50,
"hop_failures": sum(1 for x in hop if x == float("inf")),
"budget_ms": args.budget_ms,
"decision": decide(statistics.median(local), hop_p50, args.budget_ms),
}
args.out.write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
Point the local command at your real inference wrapper. Point the URL at a health endpoint you control. Never attach repository text to the probe body.
python3 local_vs_hop.py \
--local-cmd python3 -c "import time; time.sleep(0.05)" \
--remote-url https://example.invalid/health \
--budget-ms 450 \
--rounds 7
Replace the sleep with the wrapper you actually run. Replace the invalid host with a probe you own. Keep the JSON beside the repo, not inside a chat log.
The JSON file is the record that matters. Median latency beats a single lucky pulse. Failure counts belong beside those medians, always.
If hop_p50_ms is null, the wire failed closed. Stay on disk and keep editing. That brownout is the offline path doing its job.
A free remote path still has a narrow role. It is the train after the street jams. It is not the default driveway for every keystroke.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source coding project with free model access and a free server option. Those two availability notes come from the operator; this article does not add quotas, model names, or hardware claims.
Use that remote path only after the clock agrees. Local p50 has already blown the written budget. Remote p50 plus a jitter pad still beats local, and the job carries no secrets.
The pad exists because TLS and DNS stack quietly. Eighty milliseconds is a starting guess only. Recalibrate it on the route you actually use.
Calibrate with a dry loop, not a real prompt. Use the same round count each morning. Record the decision string, then delete the probe logs.
mkdir -p /tmp/hop-clock
for i in 1 2 3; do
python3 local_vs_hop.py \
--rounds 7 \
--budget-ms 450 \
--out "/tmp/hop-clock/hop_$i.json"
done
python3 - <<'PY'
import json, pathlib, statistics
rows = [json.loads(p.read_text()) for p in pathlib.Path("/tmp/hop-clock").glob("hop_*.json")]
locals_ = [r["local_p50_ms"] for r in rows]
print("local_p50_spread_ms", max(locals_) - min(locals_))
print("decisions", [r["decision"] for r in rows])
PY
Spread tells you if the laptop is thermally honest. A cold first run can flatter local inference. Warm the machine before you trust the comparison.
Even when the hop wins, strip the tree. Send a packed task file you reviewed by hand. Leave .env, credentials, and private modules on disk.
Treat keys like a hotel safe, not checked luggage. Compute may ride after the clock says so. Identity documents stay in the room.
Offline laptops are not second class in this design. They are the control group for every later hop. If local work dies without Wi-Fi, the budget was fake.
Add a tiny guard so editors fail toward disk. The snippet is a proposal, not a shipped plugin. Wire it to your own wrapper only.
from pathlib import Path
import json
BUDGET = Path("hop_budget.json")
def allow_remote_job(has_secrets: bool) -> bool:
if has_secrets:
return False
if not BUDGET.exists():
return False
data = json.loads(BUDGET.read_text())
return data.get("decision", "").startswith("hop:")
The decide() checks below are also proposals. They encode the gates in code you can read. They do not prove production safety on your network.
def test_decide_stays_offline_on_dead_wire():
assert decide(120.0, float("inf"), 450.0).startswith("offline")
def test_decide_stays_local_inside_budget():
assert decide(200.0, 40.0, 450.0).startswith("local")
def test_decide_hops_only_when_remote_is_materially_faster():
assert decide(900.0, 40.0, 450.0).startswith("hop")
def test_decide_rejects_a_marginal_hop():
assert decide(500.0, 470.0, 450.0).startswith("local")
Run those four assertions after each harness change. A broken decision function is worse than a slow laptop. The clock is only useful if the gates stay boring.
Limitations sit in the timing shape itself. The harness times a ping, not full generation. Full generation adds queue, decode, and retry tails.
Recheck the budget on a representative job size. A health endpoint can look fast and still starve. Probe the real job shape before you ship work.
Remote health is not remote codegen. A fast /health can hide a slow queue. Treat a green probe as necessary, never sufficient.
Do not use this method to justify leaking source. A faster hop that exports the tree is still a loss. Speed does not outrank the repo door.
People in regulated air gaps should skip the hop entirely. People who need a vendor SLA should skip a free server. People without a local baseline command should not compare.
Teams that cannot name their secrets should not hop either. If you cannot list what must never leave disk, stay local. Ambiguity is not a latency budget.
Write the budget in the repo and keep it dull. Re-run the harness when the route changes. Let the clock, not the feed, pick the path.
If a hop already earned that trip, MonkeyCode's free model access and free server option can host the reviewed job. Keep the probe, the secrets, and the default loop on disk.