Interactive completions belong inside the local editor process first. Remote compute should only accept scrubbed, offline-tolerant batches.
The tab key is a reflex, not a shipping document. Reflexes die when they must cross a building or an ocean.
This article splits AI coding work into two job shapes. Completions stay in-process, while refactors may travel after measurement.
The split is not ideology but a latency and secrecy budget. Blur the two shapes and the editor starts waiting on weather.
Treat the next hour of editing as a closed control loop. Each keystroke expects an answer before attention drifts to email. A remote model inserts DNS, TLS, queues, and retry storms.
Those hops do not belong in the inner loop of an editor. A batch job has a different nervous system and a different clock. Hold it, scrub it, and collect results after the build finishes.
A spinal reflex should not mail a letter for advice. Overnight crate shipping can wait for a clean dock and a dry road. That is the whole local-first argument in one picture.
Before any remote call, run three cheap measurements on your desk. They cost seconds and they prevent a week of leaked fixtures.
Probe the hop
Start with round-trip time, because interaction dies on the first extra hop. A completion that waits on DNS cannot feel like a keystroke.
# Unexecuted template. Point URL at a host you already use.
# Do not treat sample timings from another network as yours.
for i in 1 2 3 4 5; do
curl -s -o /dev/null --max-time 5 \
-w "n=${i} connect=%{time_connect} total=%{time_total}\n" \
"https://example.com/"
done
Run the probe on the same network you actually edit from. Coffee-shop Wi-Fi and office Ethernet will not agree on hops.
Read printed RTT as a veto for interactive work, not a trophy. Classic interface research treats about 100ms as instantaneous feel. That hundred-millisecond line is a heuristic, not a bench from this account.
If median hop exceeds that band, keep every tab completion local. Do not argue with the WAN during a rename across twelve files.
Scrub the crate
The second measurement is secret surface, and it is wider than .env. Snapshot tests often embed customer names inside otherwise boring JSON.
Migration logs embed host paths that map your internal network. Agent traces embed the command that printed those names and paths. A batch that still contains those traces has no honest remote route.
# Unexecuted snippet. Pair with job_route.py below.
SECRET_RES = [
re.compile(rb"AKIA[0-9A-Z]{16}"),
re.compile(rb"-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----"),
re.compile(rb"(?i)(api[_-]?key|secret|passwd|password|token)\s*[:=]\s*\S+"),
re.compile(rb"(?i)bearer\s+[a-z0-9._\-]{20,}"),
]
Treat a single hit as a closed gate, not a debate prompt. Rewrite the archive until the scanner prints a clean receipt.
The third measurement is the offline window of the current desk. A train tunnel does not care about a remote inference queue.
Flaky LTE turns a batch into a half-written remote object. Keep the archive on disk until the upload can finish in one pass.
Job size still matters after latency and secrecy both pass. A two-line rename is not a batch even on a perfect link. A whole-repo documentation pass can be a batch if the tree is clean.
Local cores may already be hot from tests, indexes, and browsers. That heat is a reason to consider overflow, not a reason to skip scrubbing.
When those three clocks pass, a free remote path can be rational. MonkeyCode currently offers free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
Use that option only as an overflow dock for already-scrubbed batches. No model names or quotas are required to apply the routing rule.
Encode the route
The router below encodes the same rule so a teammate can rerun it. It is a template. It is not a benchmark from this account.
#!/usr/bin/env python3
"""job_route.py — local interactive vs hold vs remote batch candidate.
Label: template. Run it on your laptop. Do not cite this file as evidence.
"""
from __future__ import annotations
import argparse
import re
import statistics
import subprocess
import sys
from pathlib import Path
SECRET_RES = [
re.compile(rb"AKIA[0-9A-Z]{16}"),
re.compile(rb"-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----"),
re.compile(rb"(?i)(api[_-]?key|secret|passwd|password|token)\s*[:=]\s*\S+"),
re.compile(rb"(?i)bearer\s+[a-z0-9._\-]{20,}"),
]
SKIP_DIRS = {".git", "node_modules", "dist", "__pycache__", ".venv", ".tox"}
SKIP_SUFFIX = {".png", ".jpg", ".jpeg", ".woff", ".woff2", ".zip", ".gz", ".pyc"}
def iter_files(root: Path):
for path in root.rglob("*"):
if not path.is_file():
continue
if any(part in SKIP_DIRS for part in path.parts):
continue
if path.suffix.lower() in SKIP_SUFFIX:
continue
yield path
def scan_secrets(root: Path) -> list[str]:
hits: list[str] = []
for path in iter_files(root):
try:
blob = path.read_bytes()[:1_000_000]
except OSError:
continue
if any(cre.search(blob) for cre in SECRET_RES):
hits.append(str(path.relative_to(root)))
return hits
def payload_bytes(root: Path) -> int:
total = 0
for path in iter_files(root):
try:
total += path.stat().st_size
except OSError:
continue
return total
def sample_rtt_ms(url: str, n: int) -> list[float]:
times: list[float] = []
for _ in range(n):
try:
proc = subprocess.run(
[
"curl", "-s", "-o", "/dev/null", "-w", "%{time_total}",
"--max-time", "5", url,
],
check=False,
capture_output=True,
text=True,
)
except FileNotFoundError:
raise SystemExit("curl is required for the RTT probe")
if proc.returncode != 0:
continue
try:
times.append(float(proc.stdout.strip()) * 1000.0)
except ValueError:
continue
return times
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--payload", required=True, type=Path)
parser.add_argument("--url", default="")
parser.add_argument("--samples", type=int, default=5)
parser.add_argument("--min-batch-bytes", type=int, default=32_000)
parser.add_argument("--shape", required=True, choices=["interactive", "batch"])
args = parser.parse_args()
root = args.payload.expanduser().resolve()
if not root.is_dir():
print("payload must be a directory", file=sys.stderr)
return 2
hits = scan_secrets(root)
size = payload_bytes(root)
rtt = None
if args.url:
samples = sample_rtt_ms(args.url, args.samples)
if samples:
rtt = statistics.median(samples)
if args.shape == "interactive":
decision = "LOCAL_INTERACTIVE"
why = "keystroke-loop"
elif hits:
decision = "LOCAL_HOLD"
why = f"secret_hits={len(hits)}"
elif not args.url or rtt is None:
decision = "LOCAL_HOLD"
why = "link_unproven"
elif size < args.min_batch_bytes:
decision = "LOCAL_HOLD"
why = f"too_small={size}"
else:
decision = "REMOTE_BATCH_CANDIDATE"
why = f"size={size}; rtt_ms={rtt:.1f}"
print(f"decision={decision} why={why}")
for rel in hits[:8]:
print(f"hit={rel}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Pass a payload directory and an optional timing URL from your shell. The script prints LOCAL_INTERACTIVE, LOCAL_HOLD, or REMOTE_BATCH_CANDIDATE for each run.
LOCAL_INTERACTIVE means the work still looks like a keystroke loop. LOCAL_HOLD means secrets or a dead link still block export. REMOTE_BATCH_CANDIDATE means overflow is allowed, not that it is mandatory.
# Unexecuted local test plan with fake, non-live values only.
mkdir -p /tmp/job-clean /tmp/job-dirty
printf 'title: demo\n' > /tmp/job-clean/note.md
printf 'password=demo-not-real\n' > /tmp/job-dirty/note.md
python3 job_route.py --payload /tmp/job-dirty --shape batch --url "https://example.com/"
python3 job_route.py --payload /tmp/job-clean --shape batch --url "https://example.com/"
python3 job_route.py --payload /tmp/job-clean --shape interactive --url "https://example.com/"
Run the scanner and confirm the dirty tree stays on LOCAL_HOLD. Confirm a clean batch tree can print REMOTE_BATCH_CANDIDATE.
Then add a sleep on the timing URL or unplug the network adapter. The router should flip from candidate back to hold without debate.
Failure mode one is classifying chat as a batch because the prompt looks long. Length is not shape; shape is whether a human waits in the loop.
Failure mode two is trusting a regex and missing base64 blobs. Encoded secrets will ride inside screenshots, notebooks, and golden files.
Failure mode three is measuring RTT once on a quiet Sunday backbone. Re-run the probe at the hour you actually intend to export.
Limits
Who should ignore this workflow sits in three practical camps. People with a local model that already meets the inner loop should stay put. People under HIPAA, PCI, or similar review should not export batches this way.
Incident responders staring at unknown production data should also stay local. A free server is not a contract or an audit log.
The method also assumes curl and Python exist on the laptop. If those probes cannot run, you cannot prove the route.
A regex scrubber is not a compliance program or a legal review. Teams that cannot name the data class in the tree should not export at all.
None of this is a ranking of models or a promise about free capacity. Capacity changes. The job-shape rule does not need to change with it.
Keep the reflex off the WAN even when a remote dock looks idle. Export only crates that can survive delay, inspection, and a dropped socket.
If overflow remains after a clean receipt, try a free server as a dock. Do not move the tab key there with the crates.
Run the probes on the next oversized refactor before you open a remote tab. If the router prints HOLD, the laptop is still the honest computer.
Top comments (0)