The office light stayed on well past eleven. A solo founder watched the terminal cursor blink. The next release still lacked a migration script.
A coding model sat ready to write one. One bad command could wipe the staging box. A surprise cloud bill could erase remaining runway.
That founder did not need a swarm of agents. The job was a patch, tests, and sleep. Recent agent writeups celebrate tools that roam freely.
Indie shipping treats every tool as leased equipment. A lease ends when the night's work ends. Nothing extra should outlive the night's commit.
Shipping today means the ceremony must fit one evening. A multi-agent mesh will not fit that evening. The permit file is the whole architecture tonight.
City crews do not pour concrete on a whim. They file a permit and name the block. They also list hazards before the first truck.
Model commands deserve that same quiet paper trail. The desk is a small script, not a platform. Paper is slower than a raw shell tonight.
This article proposes a local permit desk. The desk reads JSON before any shell starts. Missing fields mean the command never starts.
The model must state intent, command, and assumptions. It must also name a blast radius in words. Network and outside writes both default to false.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode free model access can draft those permit fields. A free server option can host the gate overnight.
The desk still works if that product vanishes. The method lives in ordinary files. A founder can copy them without ceremony.
The permit stays boring on purpose tonight. Boring paper stops clever accidents in their tracks. Fancy agents hide the real branch behind chatter.
Most public agent demos hide the actual branch. This desk prints that branch on ordinary paper. If the branch cannot be printed, it cannot run.
{
"issued_at": "2026-09-09T02:15:00Z",
"intent": "add a reversible users.timezone column",
"cwd": "/srv/app",
"command": ["python", "-m", "pytest", "tests/test_migrations.py", "-q"],
"assumptions": [
"tests/ lives inside this repo and is not a symlink farm",
"DATABASE_URL points at local sqlite, not a shared cloud"
],
"blast_radius": "repo-local tests only",
"network": false,
"writes_outside_repo": false,
"max_runtime_sec": 120
}
The JSON looks like a building permit on purpose. Intent is the work, not the vibe. Assumptions are claims the runner will not prove.
If sqlite is not actually local, the permit is a lie. The desk does not magically verify every sentence. It only refuses incomplete or disallowed shapes.
Two assumptions is a low bar on purpose. One-liners hide fear and other missing context. A second sentence often exposes the real risk.
A clerk at city hall does not swing the hammer. The clerk checks the form, then stamps it. The stamp is a hash plus a clock.
After the stamp, a runner may start the process. No stamp means no process and no network. The founder can leave the machine and sleep.
The following gate is a proposed example. It is not a production security audit. It will not stop a determined attacker.
It will stop a sleepy model inventing kubectl. It will also stop a guessed production migrate. Those two refusals pay for the ceremony.
#!/usr/bin/env python3
"""Proposed permit desk. Not a security boundary for hostile users."""
from __future__ import annotations
import hashlib
import json
import os
import subprocess
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
ALLOWED = {
("python", "-m", "pytest"): {"network": False, "max_runtime_sec": 180},
("python", "-m", "ruff"): {"network": False, "max_runtime_sec": 60},
("git", "status"): {"network": False, "max_runtime_sec": 15},
("git", "diff"): {"network": False, "max_runtime_sec": 15},
}
REQUIRED = (
"issued_at",
"intent",
"cwd",
"command",
"assumptions",
"blast_radius",
"network",
"writes_outside_repo",
"max_runtime_sec",
)
def fail(msg: str) -> None:
print(f"PERMIT DENIED: {msg}", file=sys.stderr)
raise SystemExit(2)
def load_permit(path: Path) -> dict:
data = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(data, dict):
fail("permit must be an object")
for key in REQUIRED:
if key not in data:
fail(f"missing field {key}")
if not isinstance(data["command"], list) or not data["command"]:
fail("command must be a non-empty argv list")
if not isinstance(data["assumptions"], list) or len(data["assumptions"]) < 2:
fail("need at least two written assumptions")
if data["writes_outside_repo"] is not False:
fail("writes_outside_repo must be false")
if not isinstance(data["intent"], str) or len(data["intent"].strip()) < 12:
fail("intent is too thin to stamp")
return data
def match_allowlist(argv: list[str]) -> dict:
argv_t = tuple(argv)
for prefix, rules in ALLOWED.items():
if argv_t[: len(prefix)] == prefix:
return rules
fail(f"command not on the lease: {argv!r}")
def assert_cwd(cwd: str) -> Path:
repo = Path.cwd().resolve()
target = Path(cwd).resolve()
if target != repo and repo not in target.parents:
fail("cwd is outside the repo")
if not str(target).startswith(str(repo)):
fail("cwd escaped the repo")
return target
def stamp(permit: dict) -> str:
blob = json.dumps(permit, sort_keys=True).encode("utf-8")
digest = hashlib.sha256(blob).hexdigest()[:16]
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
return f"{now}-{digest}"
def run(permit: dict, rules: dict, cwd: Path, token: str) -> int:
if permit["network"] and not rules.get("network"):
fail("network is not leased for this command")
timeout = min(int(permit["max_runtime_sec"]), int(rules["max_runtime_sec"]))
env = os.environ.copy()
env["NO_NETWORK"] = "1"
for key in list(env):
if key.endswith("API_KEY") or key.endswith("_TOKEN"):
env.pop(key)
started = time.time()
try:
proc = subprocess.run(
permit["command"],
cwd=str(cwd),
env=env,
capture_output=True,
text=True,
timeout=timeout,
check=False,
)
except subprocess.TimeoutExpired:
fail(f"runtime exceeded {timeout}s")
receipt_dir = Path("receipts")
receipt_dir.mkdir(exist_ok=True)
receipt = {
"stamp": token,
"intent": permit["intent"],
"command": permit["command"],
"returncode": proc.returncode,
"duration_sec": round(time.time() - started, 3),
"stdout_tail": proc.stdout[-2000:],
"stderr_tail": proc.stderr[-2000:],
}
out = receipt_dir / f"{token}.json"
out.write_text(json.dumps(receipt, indent=2), encoding="utf-8")
print(f"PERMIT STAMPED: {token}")
print(out.read_text(encoding="utf-8"))
return proc.returncode
def main() -> None:
if len(sys.argv) != 2:
fail("usage: permit_desk.py permits/tonight.json")
path = Path(sys.argv[1])
permit = load_permit(path)
rules = match_allowlist([str(x) for x in permit["command"]])
cwd = assert_cwd(str(permit["cwd"]))
token = stamp(permit)
raise SystemExit(run(permit, rules, cwd, token))
if __name__ == "__main__":
main()
The allowlist is the real product here. pytest may run only under a hard timeout. ruff may run against the local tree.
kubectl does not run from this desk. ssh does not run from this desk. curl stays off the list on purpose.
The model cannot just check production tonight. Checking production is how bills and outages start. Indie work cannot afford a curious agent.
The runner uses a timeout from the permit. It captures stdout into a receipt file. The receipt sits beside the permit in git.
Environment keys that look like secrets get dropped. That drop is incomplete and should stay incomplete. Real isolation needs a separate user or machine.
mkdir -p permits receipts tests
python permit_desk.py permits/tonight.json
A coding model should never see a raw shell. The prompt asks for a permit object only. The wrapper writes JSON, then calls the desk.
The block below is an unexecuted prompt template. It is not a scoreboard or a benchmark run. It is a contract the model must fill tonight.
Fill a permit object, not a shell command.
Use only this schema and these leased prefixes:
python -m pytest, python -m ruff, git status, git diff.
cwd must remain inside the repo.
network must be false.
writes_outside_repo must be false.
assumptions must contain two concrete claims.
Do not invent kubectl, ssh, curl, or cloud CLIs.
Return JSON only.
The founder pastes that block into a coding model. The model returns JSON, not a bedtime story. Extra prose around the JSON is a reject.
A tiny wrapper can enforce that reject. It loads JSON and refuses leftover markdown fences. Then it writes permits/tonight.json and stops cold.
#!/usr/bin/env python3
"""Proposed wrapper. Paste model output into stdin."""
import json
import sys
from pathlib import Path
raw = sys.stdin.read().strip()
if raw.startswith("```
"):
lines = raw.splitlines()
if lines and lines[-1].strip() == "
```":
lines = lines[1:-1]
elif lines:
lines = lines[1:]
raw = "\n".join(lines).strip()
try:
data = json.loads(raw)
except json.JSONDecodeError:
print("PERMIT DENIED: model output was not JSON", file=sys.stderr)
raise SystemExit(2)
Path("permits").mkdir(exist_ok=True)
Path("permits/tonight.json").write_text(
json.dumps(data, indent=2) + "\n", encoding="utf-8"
)
print("wrote permits/tonight.json")
A founder can chain the two scripts in one breath. The model fills stdin. The desk is the only process that may exec.
python write_permit.py < model_out.txt
python permit_desk.py permits/tonight.json
echo $?
On a free server the loop can idle politely. The laptop stays closed while tests run. Logs stream into receipts, not into a paid queue.
Do not park production secrets on that box. Do not point DATABASE_URL at a shared cloud. Local sqlite is the honest default for this path.
Cron is optional and often too eager. A single SSH command can start the desk. The founder watches one receipt and then disconnects.
A typical night then looks almost dull. The model proposes a small migration test permit. The desk stamps it and runs pytest quietly.
Pytest fails, so no deploy script appears. The model must file a new permit. Each retry leaves another receipt in git.
That paper trail is the morning postmortem kit. The founder reads receipts with coffee nearby. Failed assumptions show up as refused stamps.
Successful runs show the exact argv list. Nothing in the log is a vibe summary. The command either ran or it did not.
Keep the bill at zero and accept limits. Free model drafts will still miss many edge cases. Free servers will sleep, restart, or pause anyway.
When the box sleeps, the permit just waits. The founder reruns the same JSON later. Idempotent tests make that later retry safe.
Limitations follow, and they are not small. The desk trusts the allowlist and the kernel. A command that is allowed can still do harm.
pytest plugins can execute some surprising local code. A malicious test could still touch local disk. The desk is a seatbelt, not an airbag.
It also trusts the clock on the box. It does not prove the model told truth. False assumptions can still pass the schema.
Shared laptops make the stamp weaker still. Anyone who can write permits can run tools. The workflow assumes one tired founder, not a crowd.
This desk does not fit a bank. It does not fit a hospital either. Regulated shops need real isolation and review.
It does not fit production deploys tonight. It does not fit secret rotation work either. Those jobs need humans in the loop.
Agent platforms that browse the open web stay out. This pattern is for a repo on a leash. The leash is the point for indie work.
Do not wire this desk to payment APIs. Do not let it send customer mail. Do not let it scale itself with new vendors.
The swarm never had to ship this patch. The permit desk had to ship this patch. That is enough for a one-person release.
The migration finally lands as a small diff. Receipts record that kubectl never appeared. The staging box still exists in the morning.
Top comments (0)