The founder opened a laptop just after eleven. A coding agent had rewritten half the checkout path. The diff looked confident, clean, and completely unaffordable.
It added Redis, Stripe webhooks, and a queue worker. The live stack was SQLite, a cron job, and patience. No one had approved a new vendor that night.
Indie shipping still rewards speed more than ceremony. Speed without a fence still spends money. The bill arrives after the merge, never before.
Agent write-ups keep repeating the same expensive bruise. Models often fill silence with popular infrastructure guesses. Those guesses cluster around queues, caches, and paid APIs.
A charter treats the agent like a junior contractor. The contractor writes code only after listing guesses. Unverified guesses block the patch, not the founder.
Think of a blank purchase order on a warehouse floor. Anyone can order forklifts if the form stays blank. A filled form makes the expensive items visible first.
The workflow below is a worked example, not a production benchmark. It uses a charter file, an assumption ledger, and a checker. A free server can host the checker for a solo founder.
{
"project": "cart-lite",
"max_files_touched": 6,
"allowed_deps": ["flask", "sqlite3"],
"allowed_env": ["DATABASE_PATH", "PUBLIC_BASE_URL"],
"forbidden_substrings": [
"stripe", "redis", "sqs", "rds", "openai", "mongodb+srv"
],
"allowed_paths": ["app.py", "templates/**", "static/**", "lib/**"]
}
The charter file stays deliberately boring on purpose. Boring policy files survive Friday night fatigue. Flashy policy engines usually do not survive.
Before any patch, the agent must emit a ledger. The ledger uses JSON, one object per guess. Each guess needs a status of verified or blocked.
{
"assumptions": [
{
"id": "A1",
"claim": "Checkout can store carts in local SQLite.",
"status": "verified",
"evidence": "app.py already opens DATABASE_PATH"
},
{
"id": "A2",
"claim": "Card charges require a Stripe webhook worker.",
"status": "blocked",
"evidence": "charter forbids stripe"
}
],
"files": ["app.py", "lib/cart.py"],
"new_deps": []
}
A blocked row is a feature, not a failure. It records the temptation in plain language. The founder can later accept the cost on purpose.
The prompt stays equally strict and short. It forbids code until the ledger validates. The example prompt below is a template, not production advice.
You are a coding agent for a solo shop.
Read charter.json before you invent infrastructure.
Output assumption_ledger.json first, then a unified diff.
Do not add dependencies outside allowed_deps.
Mark every external service as verified or blocked.
If a guess is blocked, omit it from the diff.
Touch at most max_files_touched paths.
Stay inside allowed_paths.
The founder pastes the prompt and the charter into the model. The model must return the ledger before any hunk. Extra prose around the JSON gets stripped by a small cut script.
#!/usr/bin/env python3
"""Worked example: pull the first JSON object from model output."""
import json
import re
import sys
text = sys.stdin.read()
match = re.search(r"\{[\s\S]*\}", text)
if not match:
raise SystemExit("no json object found")
obj = json.loads(match.group(0))
json.dump(obj, sys.stdout, indent=2)
sys.stdout.write("\n")
The checker is a Python script with only the standard library. It reads the charter, the ledger, and the diff. It exits nonzero when the agent smuggles a bill.
#!/usr/bin/env python3
"""Worked example: reject patches that invent paid infrastructure."""
from __future__ import annotations
import json
import pathlib
import re
import sys
UNIFIED_FILE = re.compile(r"^\+\+\+ b/(.+)$", re.M)
ADDED_LINE = re.compile(r"^\+[^+].*", re.M)
IMPORT_LINE = re.compile(
r"^\+\s*(?:import|from)\s+([A-Za-z0-9_\.]+)", re.M
)
def load_json(path: pathlib.Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def files_in_diff(diff: str) -> list[str]:
return UNIFIED_FILE.findall(diff)
def added_text(diff: str) -> str:
return "\n".join(ADDED_LINE.findall(diff)).lower()
def check(charter: dict, ledger: dict, diff: str) -> list[str]:
errors: list[str] = []
files = files_in_diff(diff)
if len(files) > int(charter["max_files_touched"]):
errors.append("too many files in this patch")
for path in files:
allowed = False
for glob in charter["allowed_paths"]:
if pathlib.PurePath(path).match(glob):
allowed = True
if not allowed:
errors.append(f"path outside charter: {path}")
claimed = set(ledger.get("files") or [])
if claimed != set(files):
errors.append("ledger files do not match the diff")
if ledger.get("new_deps"):
extra = set(ledger["new_deps"]) - set(charter["allowed_deps"])
if extra:
errors.append(f"new deps not allowed: {sorted(extra)}")
body = added_text(diff)
for needle in charter["forbidden_substrings"]:
if needle.lower() in body:
errors.append(f"forbidden token in added lines: {needle}")
for row in ledger.get("assumptions") or []:
if row.get("status") not in {"verified", "blocked"}:
errors.append(f"assumption {row.get('id')} lacks a status")
if row.get("status") == "verified" and not row.get("evidence"):
errors.append(f"assumption {row.get('id')} has no evidence")
if row.get("status") != "blocked":
continue
claim = (row.get("claim") or "").lower()
for needle in charter["forbidden_substrings"]:
if needle in claim and needle in body:
errors.append(f"blocked claim still landed: {needle}")
for match in IMPORT_LINE.finditer(diff):
root = match.group(1).split(".")[0]
if root not in charter["allowed_deps"] and root not in {
"json", "os", "re", "sys", "pathlib", "typing"
}:
errors.append(f"import not in allowed_deps: {root}")
return errors
def main() -> int:
if len(sys.argv) != 4:
sys.stderr.write(
"usage: check_charter.py charter.json ledger.json patch.diff\n"
)
return 2
charter = load_json(pathlib.Path(sys.argv[1]))
ledger = load_json(pathlib.Path(sys.argv[2]))
diff = pathlib.Path(sys.argv[3]).read_text(encoding="utf-8")
errors = check(charter, ledger, diff)
if errors:
sys.stderr.write("charter failed\n")
for item in errors:
sys.stderr.write(f"- {item}\n")
return 1
sys.stdout.write("charter ok\n")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Run it locally before the agent ever sees main. Keep main protected until the checker returns zero.
python3 check_charter.py charter.json assumption_ledger.json patch.diff
echo $?
The founder can prove the checker with two diffs. The first diff only edits app.py and stays inside SQLite. The second diff sneaks a Stripe import into lib/cart.py.
Save the two diffs as pass.diff and fail.diff. The first command must print the charter ok line. The second command must exit one and mention stripe.
A zero on exit means the guess list and the diff agree. A one means the agent tried to order forklifts. The founder reads the stderr lines, then decides.
Solo founders still need the laptop to sleep. A tiny stdlib server can hold the checker. The example below is a sketch, not a hardened service.
#!/usr/bin/env python3
"""Worked example: POST ledger+diff, get a pass or fail JSON body."""
from __future__ import annotations
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from check_charter import check, load_json
CHARTER = load_json(Path("charter.json"))
class Handler(BaseHTTPRequestHandler):
def do_POST(self) -> None:
if self.path != "/check":
self.send_error(404)
return
length = int(self.headers.get("Content-Length", "0"))
raw = self.rfile.read(length)
try:
payload = json.loads(raw.decode("utf-8"))
errors = check(CHARTER, payload["ledger"], payload["diff"])
except (KeyError, ValueError, json.JSONDecodeError):
self.send_error(400)
return
body = json.dumps({"ok": not errors, "errors": errors}).encode()
self.send_response(200 if not errors else 422)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(body)
if __name__ == "__main__":
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
A curl call then becomes the whole review gate for a nap. The founder can close the lid without watching the model.
curl -sS -X POST http://127.0.0.1:8080/check \
-H 'Content-Type: application/json' \
--data-binary @payload.json
The payload file holds the ledger object and the unified diff. Keep that endpoint off public ingress without a secret path. A free server is a spare outlet, not a castle.
Some founders place that checker beside a free coding model. MonkeyCode offers free model access and a free server option if that loop helps. Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The model drafts the ledger and the diff. The server only scores them against the charter. If that sandbox vanished tomorrow, the charter file would still pay rent.
Limits stay sharp, because the method is shallow. Substring bans miss renamed imports and encoded URLs. A determined model can launder Stripe through a helper name.
Verified ledger rows can still be false. Written evidence text is still not proof. The checker does not execute tests or open the database.
Forbidden token lists rot as vendors rename products. The founder must prune the list monthly. An outdated charter remains expensive theater anyway.
This approach does not fit every software shop. Teams with PCI, HIPAA, or SOC reviews need real review. People who need uptime contracts should skip the free server.
It also fails when the product truly needs a paid queue. The charter would block the honest design. In that case, edit the charter in daylight, then generate.
Agent chatter keeps celebrating bigger autonomous loops today. Those bigger loops invent far more silent purchases. A one-file charter is a smaller loop with a receipt.
The founder at eleven did not need another framework. The founder needed a stop sign the model could not shrug off. The ledger is that stop sign, written in JSON.
Ship the charter before the first agent commit. Let the model type only after guesses are visible. The credit card can stay in the drawer.
Top comments (0)