Remote help is not free if the hop is slow. A prompt that never leaves disk cannot leak. The cheap path is a local cache with a measured fill.
Most coding assistants resend the same context twice. The second trip pays latency already spent. Hash the prompt and the file set first. Store the draft beside the repo. Cross the wire only on a miss.
This is not a model-quality argument. It is a path argument. Latency, secrets, and offline time decide the path. A free remote server wins only after those three checks pass.
Teams keep scoring models in public threads. They under-score the path those models travel. A stale hop can waste more time than a weak draft. A leaked token can waste more than time.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option. Those two facts matter only after a local ledger says the hop is clean. The ledger still works if the fill URL is a stub.
Treat the working tree as the source of truth. Treat the network as a spare lane. The spare lane opens on a cache miss. It stays closed when secrets match, when the probe is slow, or when the machine is offline.
The artifact is a content-addressed preflight. It hashes the prompt text and a sorted file list. It binds that hash to git rev-parse HEAD. It refuses to fill if a secret pattern hits. It probes one round trip before any payload moves.
Label the numbers below as a method, not a result. This draft does not claim a measured speedup. Run the script on your repo. Record your own timings. Do not borrow someone else's hop tax.
Create a directory that never gets committed. Keep drafts next to the work, not in git. A cache is a notebook, not a source of record.
mkdir -p .prompt-cache
echo ".prompt-cache/" >> .git/info/exclude
git rev-parse HEAD
python3 --version
The exclude line keeps drafts off the remote. The HEAD binding keeps drafts honest. A hash without HEAD will replay a draft onto the wrong tree. That failure looks like a smart model. It is a stale cache.
Here is a compact hasher you can run locally. It reads stdin as the prompt. It reads file paths as extra context. It writes a ledger line that later steps can parse.
# preflight_hash.py — run locally; do not ship secrets
import hashlib, json, os, sys, time
from pathlib import Path
SECRET_MARKERS = (
"BEGIN PRIVATE KEY",
"AWS_SECRET_ACCESS_KEY",
"xoxb-",
"ghp_",
)
def sha(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def scan(text: str) -> list[str]:
hits = [m for m in SECRET_MARKERS if m in text]
return hits
def read_files(paths: list[str]) -> bytes:
blob = b""
for p in sorted(paths):
blob += Path(p).read_bytes()
blob += b"\n--\n"
return blob
def main() -> None:
prompt = sys.stdin.read()
files = sys.argv[1:]
head = os.popen("git rev-parse HEAD").read().strip()
payload = prompt.encode() + b"\0" + read_files(files)
digest = sha(payload)
hits = scan(prompt) + scan(payload.decode("utf-8", "replace"))
record = {
"head": head,
"digest": digest,
"bytes": len(payload),
"files": len(files),
"secret_hits": hits,
"ts": int(time.time()),
}
Path(".prompt-cache").mkdir(exist_ok=True)
Path(".prompt-cache/ledger.jsonl").open("a").write(
json.dumps(record) + "\n"
)
print(json.dumps(record))
if hits:
sys.exit(2)
if __name__ == "__main__":
main()
Exit code 2 means the hop is forbidden. The ledger still records the attempt. That record is the audit trail. A silent skip teaches nothing the next morning.
Wire the hasher to a tiny cache lookup. A hit returns the draft from disk. A miss may call a fill URL. The fill URL can be empty. Empty means stay local and stop.
# cache_fill.py — proposal: readers must supply FILL_URL
import json, os, sys, time, urllib.request
from pathlib import Path
def probe(url: str, timeout: float = 1.5) -> float | None:
start = time.perf_counter()
try:
urllib.request.urlopen(url, timeout=timeout)
except Exception:
return None
return time.perf_counter() - start
def main() -> None:
record = json.loads(sys.stdin.read())
digest = record["digest"]
cache = Path(".prompt-cache") / f"{digest}.txt"
if cache.exists():
sys.stdout.write(cache.read_text())
return
if record.get("secret_hits"):
raise SystemExit("refuse fill: secret hit")
url = os.environ.get("FILL_URL", "")
if not url:
raise SystemExit("cache miss and no FILL_URL")
rtt = probe(url.rstrip("/") + "/health")
if rtt is None or rtt > 0.8:
raise SystemExit(f"refuse fill: rtt={rtt}")
# Payload send belongs behind your own client.
# Keep keys on disk. Do not print them here.
print(f"miss {digest} rtt={rtt:.3f}s", file=sys.stderr)
raise SystemExit("fill client not wired in this sample")
if __name__ == "__main__":
main()
The 0.8 second gate is a local policy, not a universal law. Change it after you probe your own path. A café network fails this gate often. A wired desk may pass it. The policy should follow the link, not a blog number.
A free server wins in a narrow window. The cache is cold. The secret scan is clean. The probe is fast. The laptop is already busy on a compile. Offline work is not required today. Outside that window, disk still wins.
Think of the hop as a courier. The courier is cheap when the envelope is thin. The courier is costly when the envelope holds keys. The courier is useless when the road is down. Hashing is weighing the envelope before the door opens.
Bind a test to the hasher so the policy cannot drift. The test does not call a model. It only checks that a planted marker blocks the fill.
# test_preflight_hash.py — run: pytest -q test_preflight_hash.py
import io, json, os, subprocess, sys, textwrap
from pathlib import Path
def run_hasher(prompt: str, tmp: Path) -> subprocess.CompletedProcess:
env = os.environ.copy()
return subprocess.run(
[sys.executable, str(tmp / "preflight_hash.py")],
input=prompt,
text=True,
capture_output=True,
cwd=tmp,
env=env,
)
def test_secret_blocks_and_records(tmp_path, monkeypatch):
src = Path("preflight_hash.py").read_text()
(tmp_path / "preflight_hash.py").write_text(src)
(tmp_path / ".git").mkdir()
# Stub HEAD when git is missing in the sandbox.
monkeypatch.setenv("PATH", os.environ["PATH"])
prompt = "rotate ghp_exampletokenonly"
result = run_hasher(prompt, tmp_path)
assert result.returncode == 2
line = (tmp_path / ".prompt-cache" / "ledger.jsonl").read_text()
row = json.loads(line.splitlines()[-1])
assert "ghp_" in row["secret_hits"]
If git is absent in CI, stub git rev-parse with a fixture. The point is the fail-closed path. A green test that never sees a secret is a weak test. Plant a marker. Watch the exit code.
Byte size belongs in the ledger for a reason. A 20 KB prompt and a 2 MB dump are not the same hop. Large dumps punish both local RAM and remote queues. Trim the file list before you hash. Hashing a noisy tree teaches the cache the wrong lesson.
A practical trim is the diff, not the repo. Ask git for names of changed files. Feed only those paths to the hasher. Unchanged files already live in HEAD. They do not need to ride the courier again.
PROMPT=$(cat <<'EOF'
Summarize the risk in the staged diff only.
EOF
)
FILES=$(git diff --cached --name-only)
printf '%s' "$PROMPT" | python3 preflight_hash.py $FILES \
| python3 cache_fill.py
That pipeline is the whole workflow. It reads as one breath. Hash. Record. Refuse or fill. The assistant is downstream of the ledger. It is not the ledger.
Limitations sit in the open. SHA-256 of a prompt is not a semantic match. Two paraphrases miss the cache and pay twice. The secret list is a toy. Real scanners need your own patterns and entropy checks. The health probe is not a load test. A cheap 200 can still hide a slow generate. The sample fill client stops before it sends context. Wire that part with care.
The cache can lie if two trees share a HEAD by mistake. Worktrees and detached states need extra tags. Add git rev-parse --show-toplevel if you juggle several checkouts. Do not share .prompt-cache across machines. A draft from another clock is a rumor.
Who should not use this path. Air-gapped teams with a no-fill rule should skip the probe. They already have an answer. Regulated shops that forbid any remote model should keep FILL_URL unset. People chasing model leaderboards will hate this. The method does not rank models. It ranks paths.
Who should use it. Developers who repeat the same review prompt. People who rotate between offline trains and a desk. Anyone who has watched a key travel inside a context dump. The free server is a burst valve, not a home.
If a fill is allowed, keep credentials in a local secret store. Pass a short-lived handle to the client. Do not embed keys in the prompt. Do not log the prompt body to a shared disk. The ledger stores hashes and counts. That is enough to debug a miss.
A note on current AI talk. Measurement talk often chases bigger exams. Path talk still fits a laptop. You can hash today. You can refuse today. You can probe today. None of that waits on a new scoreboard.
Run the hasher on one real change set this week. Read the ledger line. If secret_hits is not empty, the wire did not earn the job. If the probe fails, the laptop still holds the loop. If the cache hits, you already paid once.
MonkeyCode's free model access and free server option can sit behind FILL_URL when the ledger is green. Point that URL only after the scan and the probe pass. The rest of the method does not need the product. Disk remains the default lane.
Top comments (0)