DEV Community

Harper Xu
Harper Xu

Posted on

The Generator Host Is a Separate Failure Domain

Remote generation is not inside your trust boundary. Treat the host as a foreign compiler farm. You review egress first, then you review the diff.

That sounds harsh. It is also cheap insurance. A borrowed inference box can draft without sharing your blast radius.

You still want speed. You still want a second pair of eyes. You do not want that pair inside your VPC.

Start with constraints, not model names

You have three constraints that do not negotiate. Customer data stays on your side of the wire. Apply stays on a machine you control. The generator may vanish tomorrow and your tree must still build.

Those constraints beat any demo. A free drafting host is useful only inside them. If a workflow needs that host to stay forever, you already lost.

Some teams use MonkeyCode here for the drafting plane. It provides free model access and a free server option.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

You send redacted context. You receive text. You never let that host mount your repo.

Data flow is a mail slot, not a merge

Picture a locked workshop with a mail slot. You slide a brief through the slot. A clerk you do not employ types a draft. You carry the paper home and typeset it yourself.

That is the whole architecture. Pack context on your laptop. Strip secrets at the slot. Accept text from a host you do not own. Apply only after local review.

Sketch the path before you write scripts. If you cannot draw it, you cannot review it.

[working tree + secrets]  ->  packer  ->  redactor
        |                                      |
        |                                      v
        |                              [brief.md] --egress--> [generator host]
        |                                      |
        |                                      v
        |                              [draft.diff + receipt]
        v                                      |
[review + tests + apply]  <---- never mounts --+
Enter fullscreen mode Exit fullscreen mode

The arrows matter more than the boxes. Egress is one way. Apply never points back at the host. The receipt is the only souvenir you keep.

Here is a packing step you can run before any remote call.

#!/usr/bin/env bash
# pack_context.sh — build a redacted brief, never ship the tree
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
ALLOW="$ROOT/brief.allow"
OUT="${TMPDIR:-/tmp}/brief-$(git rev-parse --short HEAD).md"

{
  echo "# Task"
  echo "$1"
  echo
  echo "# Allowed paths"
  if [[ -f "$ALLOW" ]]; then
    cat "$ALLOW"
  else
    git diff --name-only main...HEAD | head -n 20
  fi
} > "$OUT"

python3 redact_prompt.py "$OUT"
echo "$OUT"
Enter fullscreen mode Exit fullscreen mode

The script names files. It does not dump .env. It does not tar the repo. That is the point of the slot.

Keep an allowlist beside the packer. Tomorrow you will thank yourself.

# brief.allow — paths the remote host may hear about
src/payments/retry.py
src/payments/client.py
tests/payments/test_retry.py
Enter fullscreen mode Exit fullscreen mode

Now the redactor. Keep it boring. Boring gates survive Friday deploys.

# redact_prompt.py
import re, sys
from pathlib import Path

SECRET = re.compile(
    r"(?i)(api[_-]?key|secret|token|password)\s*[:=]\s*\S+"
)
ASSIGN = re.compile(
    r"(?i)(AWS|GITHUB|OPENAI|NPM)_[A-Z0-9_]+\s*=\s*\S+"
)

def redact(text: str) -> str:
    text = SECRET.sub("[REDACTED]", text)
    text = ASSIGN.sub("[REDACTED]", text)
    return text

path = Path(sys.argv[1])
path.write_text(redact(path.read_text()), encoding="utf-8")
Enter fullscreen mode Exit fullscreen mode

You run that on every brief. You do not run it on the model host. The host never sees the original bytes.

After the host replies, you store a receipt, not a merge. The receipt is an audit hinge. It says where text came from. It does not bless the diff.

HEAD_SHA="$(git rev-parse HEAD)"
BRIEF_HASH="$(shasum -a 256 "$OUT" | awk '{print $1}')"
PATCH_HASH="$(shasum -a 256 /tmp/draft.diff | awk '{print $1}')"

cat > /tmp/generation-receipt.json <<EOF
{
  "commit": "$HEAD_SHA",
  "brief_sha256": "$BRIEF_HASH",
  "patch_sha256": "$PATCH_HASH",
  "host": "remote-draft",
  "applied": false
}
EOF
Enter fullscreen mode Exit fullscreen mode

Then you check the patch without writing the tree. Failure on apply is your domain. The clerk already left the building.

git apply --check /tmp/draft.diff
# only after a human reads draft.diff and the receipt:
# git apply --cached /tmp/draft.diff
Enter fullscreen mode Exit fullscreen mode

Notice the host is gone by this line. Your editor, your tests, your rollback. The foreign compiler does not get a second vote.

Failure domains you should draw on a whiteboard

Draw three boxes. Do not draw one happy pipeline. The laptop holds secrets, keys, and the working tree. The generator host holds prompts, drafts, and logs you do not control. CI holds tests, signatures, and the right to merge.

If the generator host burns, your repo still boots. If the laptop leaks, the host should hold nothing durable. If CI is wrong, you still refuse an unsigned apply.

That split is the review. Most teams skip it. They paste a stack trace into a chat box. The chat box becomes a fourth domain with no owner.

Watch retention. A free shared host may keep prompts. You should assume it does. Design the brief as if it will be read aloud in a hallway.

Here is a tiny test that fails loud when a brief still carries secrets.

# test_brief_redaction.py
from pathlib import Path
import re, sys

brief = Path(sys.argv[1]).read_text(encoding="utf-8")
needles = [
    r"(?i)api[_-]?key\s*[:=]\s*\S+",
    r"(?i)password\s*[:=]\s*\S+",
    r"AKIA[0-9A-Z]{16}",
    r"-----BEGIN (RSA |OPENSSH )?PRIVATE KEY-----",
]
for n in needles:
    if re.search(n, brief):
        raise SystemExit(f"secret pattern still in brief: {n}")
print("brief is clean enough to leave the laptop")
Enter fullscreen mode Exit fullscreen mode

Run it in the same breath as packing. Do not send a brief that failed this gate. Do not skip the gate because the task feels small.

OUT="$(./pack_context.sh "add retry around the payment client")"
python3 test_brief_redaction.py "$OUT"
# only then: send "$OUT" to the drafting host
Enter fullscreen mode Exit fullscreen mode

You now have a reproducible fence. It is not a product tour. It is architecture you can rerun on Monday.

What this architecture refuses to do

It refuses to stream your tree. It refuses to let the host write files. It refuses to treat fluency as review. Those refusals are features.

It also refuses to promise uptime. A free server is a favor, not a cluster. You keep a local fallback for the same brief format. When the host is gone, the packing scripts still work.

Regex redaction is not a DLP product. It will miss novel secret shapes. It will not understand screenshots. It will not save you from pasting a dump by hand.

Do not put regulated health data in the brief. Do not put unreleased keys in the brief. Do not put production connection strings in the brief. If your threat model forbids any egress, stop here. Use a machine that never routes out.

This pattern is wrong for embargoed kernel work. It is wrong for incident response with live credentials. It is wrong when you cannot explain the diff in standup. It is wrong if apply must happen inside the same process that drafted the text.

What you would change next

First, make the allowlist mandatory. Today the packer can fall back to a diff name list. Tomorrow a missing brief.allow should exit nonzero.

Second, sign the receipt. A detached signature should tie brief hash, patch hash, and head commit. CI can reject apply without that triple. Unsigned drafts stay in /tmp.

Third, replay locally. Keep the brief format stable. When you later run a local model, the same packer feeds it. The host becomes swappable. The fence stays.

Fourth, bound log retention on your side. Delete briefs after merge. The remote host may keep copies. Your laptop should not pile them beside the repo.

None of that needs a bigger model. It needs a smaller blast radius. Architecture work is mostly saying no to extra arrows.

A short review you can repeat

Ask four questions before any remote draft. What leaves the laptop. What the host can retain. What apply is allowed to touch. What you will do when the host disappears.

If you cannot answer those, you are not reviewing architecture. You are hoping. Hope is not a failure domain.

The generator is a foreign compiler. You already know how to treat those. You sandbox them. You inspect object files. You do not let them login.

Keep that habit when the compiler starts talking. The sentence that feels like a teammate is still just bytes crossing a trust line.

If you need a drafting host off your runtime network, try the free server option and keep apply local.

Top comments (0)