You should keep generation off your runtime network. That constraint beats a pile of prompt rules. A remote coding hop is only a worker.
It is not a cluster peer. You do not share routes with it. You do not share identity with it.
Teams invert this under deadline pressure. They drop a model box beside app nodes. Then secrets leak through routes nobody drew.
Think of a print shop downtown. You send a manuscript in a sealed envelope. You collect pages at the counter later. You never hand the shop your house key.
A generation host is that shop. It may read a redacted bundle. It may emit a tarball. It must not open a path back.
The constraints you actually have
Runtime owns state, traffic, and identity. Generation owns none of those three. Mix them and you create one failure domain.
Your editor may sit on a trusted laptop. Your CI may sit on a trusted runner. Neither should mount the generation host as a sibling.
You also lack a durable capacity story. Free hops vanish, throttle, or move. Design the hop as disposable from day one.
Do not store cluster kubeconfigs on that box. Do not store cloud roles on that box. Do not store production DNS on that box.
The only credential it may see is narrow. Scope it to one job identifier. Expire it when the tarball lands.
Current coding agents tempt a worse shape. They want a loop that can touch tools. A loop with cluster reach is a merged domain.
You can still use a model for drafts. You cannot let the draft host route packets. Drafts travel as files, not as sessions.
Data flow for one generation job
Draw the path as a one-way valve. Trusted side packs a bundle. Trusted side pushes that bundle out.
The generation host writes artifacts to a drop folder. Trusted side pulls the folder later. Trusted side verifies before any merge.
Nothing on the generation host starts runtime calls. No webhooks into prod. No git push with a deploy key.
Name the four hops in plain language. Pack lives on trusted ground. Generate lives on the isolated host. Fetch returns to trusted ground. Gate decides merge or reject.
Here is a packer that strips secrets first. Treat it as an example, not a hardened product. Run it on the trusted side only.
# pack_prompt.py — example packer, unexecuted in your org until you review it
from pathlib import Path
import hashlib, json, tarfile, os, re, time
ROOT = Path(".").resolve()
OUT = Path("/tmp/gen-job")
DENY = re.compile(r"(BEGIN (RSA |OPENSSH )?PRIVATE KEY|AKIA[0-9A-Z]{16}|api[_-]?key\s*=)", re.I)
SKIP = {".env", ".pem", ".key", "kubeconfig", "id_rsa"}
def allowed(path: Path) -> bool:
if any(path.name.endswith(s) or path.name == s for s in SKIP):
return False
rel = path.relative_to(ROOT).as_posix()
return not rel.startswith((".git/", "node_modules/", "secrets/"))
def pack(job_id: str, paths: list[str]) -> Path:
OUT.mkdir(parents=True, exist_ok=True)
staging = OUT / job_id
if staging.exists():
raise SystemExit("job id already packed")
staging.mkdir()
manifest = {"job_id": job_id, "files": [], "packed_at": int(time.time())}
for raw in paths:
src = (ROOT / raw).resolve()
if ROOT not in src.parents and src != ROOT:
raise SystemExit(f"path escapes workspace: {raw}")
if not src.is_file() or not allowed(src):
continue
text = src.read_text(errors="replace")
if DENY.search(text):
raise SystemExit(f"secret-like content in {raw}")
dest = staging / src.relative_to(ROOT)
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(text)
digest = hashlib.sha256(text.encode()).hexdigest()
manifest["files"].append({"path": str(src.relative_to(ROOT)), "sha256": digest})
(staging / "manifest.json").write_text(json.dumps(manifest, indent=2))
tar_path = OUT / f"{job_id}.tar.gz"
with tarfile.open(tar_path, "w:gz") as tar:
tar.add(staging, arcname=job_id)
print(tar_path)
print(hashlib.sha256(tar_path.read_bytes()).hexdigest())
return tar_path
if __name__ == "__main__":
job = os.environ.get("GEN_JOB_ID", "job-demo")
pack(job, ["README.md", "src/app.py", "prompts/task.md"])
You copy that tarball onto the generation host. You do not copy your shell history. You do not copy ~/.ssh. You do not copy CI OIDC tokens.
The generation host should see a short job token only. That token writes one drop object. It cannot read other jobs. It cannot talk to your API.
# trusted side: copy the bundle out, never mount $HOME
job="${GEN_JOB_ID:?set GEN_JOB_ID}"
install -m 600 /tmp/gen-job/${job}.tar.gz ./outbox/${job}.tar.gz
sha256sum ./outbox/${job}.tar.gz | tee ./outbox/${job}.sha256
# isolated host: work inside a disposable directory
mkdir -p /var/gen/${job}/drop
tar -C /var/gen/${job} -xzf /inbox/${job}.tar.gz
# run your generator against /var/gen/${job}/${job} only
# write /var/gen/${job}/drop/artifact.tar.gz when finished
Pull stays on trusted ground. You fetch bytes. You check the digest. You inspect before merge.
# fetch_and_gate.sh — example gate, review before you use it
set -euo pipefail
job="${GEN_JOB_ID:?}"
expect="$(cut -d ' ' -f1 < outbox/${job}.sha256)"
curl -fsS "https://gen-drop.example/${job}/artifact.tar.gz" -o /tmp/${job}-art.tar.gz
got="$(sha256sum /tmp/${job}-art.tar.gz | awk '{print $1}')"
test "$got" = "$expect" || { echo "digest mismatch"; exit 2; }
mkdir -p /tmp/${job}-art
tar -C /tmp/${job}-art -xzf /tmp/${job}-art.tar.gz
# reject absolute paths and parent traversal
if tar -tzf /tmp/${job}-art.tar.gz | grep -E '(^/)|(^\.\./)|(/\.\./)'; then
echo "unsafe member path"; exit 3
fi
# reject obvious runtime bindings inside the artifact
if grep -R -E 'kubeconfig|BEGIN OPENSSH PRIVATE KEY|AKIA[0-9A-Z]{16}' /tmp/${job}-art; then
echo "artifact looks like a secret store"; exit 4
fi
echo "gate passed for ${job}; copy into a review branch, not main"
Keep the host off the runtime bridge. Docker makes the idea visible. An internal network has no default egress to your app net.
docker network create --internal gen-plane
docker network create runtime-plane
# generation worker: no attachment to runtime-plane
docker run --rm --network gen-plane --name gen-worker \
-v $PWD/inbox:/inbox:ro \
-v $PWD/drop:/drop \
alpine:3.20 sleep 3600
# prove the worker cannot resolve or reach runtime services
docker exec gen-worker sh -c 'wget -qO- http://app.runtime-plane:8080/health || echo isolated'
If you need a fetch from trusted CI, do it from CI. Do not give the worker a callback URL. Callbacks turn the print shop into a tenant.
Failure domains, kept apart
Name the domains before you draw boxes. Runtime fails by serving wrong answers. Generation fails by emitting wrong files. The gate fails by accepting a bad tarball.
If the generation host dies, runtime still serves users. You retry the job later. You do not page the on-call for a draft box.
If the artifact is poisoned, the gate should reject it. Hash mismatch is a hard stop. Secret-like content is a hard stop. Path traversal is a hard stop.
If the job token leaks, blast radius stays small. It can drop one object at most. It cannot list cluster secrets. It cannot open SSH.
If you peer the networks, those domains collapse. One prompt injection becomes a lateral step. That is the failure you are buying.
A useful picture is a kitchen and a loading dock. The dock can receive crates. The kitchen should not share floor drains with the dock. Shared drains move the wrong fluids.
Agent loops make the drain metaphor worse. A loop that can call tools wants reach. Reach across planes is how a draft becomes an incident.
You can keep a tiny allowlist of tools on the hop. File read inside the bundle is enough. File write inside the drop folder is enough. Process spawn against production is not.
job token scope (example claim set, not a vendor format)
sub: gen-job-20260911-17
aud: drop-writer
paths: /var/gen/job-20260911-17/drop/*
methods: PUT
exp: 20 minutes
net: gen-plane only
That claim set is a design sketch. You still implement it with your own signer. Do not paste a long-lived cloud role into the hop.
What this architecture refuses to do
It refuses live deploys from model output. It refuses model-owned merge buttons. It refuses shared volumes with /var/run/secrets.
It also refuses “just this once” exceptions. A single peered debug session becomes a template. The next incident copies that template.
You review generated code on a branch. Humans merge after tests. The hop never learns your deploy credential.
Where a free remote hop fits
Some teams still want a box that is not the cluster. They want model access without parking GPUs beside app nodes. They want that box disposable.
MonkeyCode offers free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. You can park generation there as a worker, then pull artifacts through your own gate.
The product does not replace the valve. It does not own your runtime routes. Treat any free hop as capacity you may lose without notice.
If you try that hop, keep it off your runtime routes. Copy bundles in. Pull tarballs out. Leave identity at home.
What I would change next
This sketch still trusts a single digest file. I would split pack identity from fetch identity. I would sign the drop with a key the hop cannot read.
I would add a content-type allowlist per path. Markdown and source files may pass. Shell installers and Terraform applies would not pass cold.
I would record a generation bill of materials. Job id, bundle digest, artifact digest, gate version. That record belongs in the pull request, not on the hop.
I would kill leftover job directories on a timer. Disposable means delete. Leftover drops become a second repository of your source.
I would also ban outbound DNS on the generation net. If the worker must fetch a tool, you vendor that tool. Surprise egress is a hidden peer.
Limits, and who should not use this
This is an isolation pattern, not a model benchmark. It does not prove output quality. It does not prove the hop will be there tomorrow.
Do not use it if policy forbids any third-party host. Do not use it if you generate inside a sealed compiler. Do not use it if the model must drive production APIs.
Do not use it as an excuse to skip review. A clean digest can still contain a logic bomb. The gate catches shape problems, not intent.
Local generation on an air-gapped laptop can be simpler. The same valve still helps there. Pack, generate, fetch, gate. The network just becomes a directory.
You wanted a remote coding hop. Keep it as a print shop. Collect the pages. Then decide, on your side, whether they enter the building.
Top comments (0)