DEV Community

Charlie Hu
Charlie Hu

Posted on

Leave the Write Path at Home: One Read, a Route Contract, and a Skip Ledger

A weekend agent demo stays showable when the public slice does one read, rejects every write, and fails closed if that contract drifts. A larger prompt does not create that boundary. A route contract, a skip ledger, and a checker that runs before any process listens do. This build log treats that cut as the whole product for the weekend. Hosting and a model call stay optional, and every skipped write is named before the demo starts.

The failure mode is familiar. A second tool appears, a scratch file changes, and the walkthrough cannot be repeated from a clean directory. Moving that surface onto a free server does not repair it. The host only makes the unfinished path easier for someone else to open.

The cut this log actually ships

The working slice is small on purpose.

  • One user-facing path looks up a note by id.
  • One optional model call summarizes that same note.
  • Mutating tools stay at zero.
  • A skip ledger records what was not built, and why.

That list is the product. A portfolio page, a multi-step agent, or a write-back bot is a different weekend. Those ideas can wait in the ledger without being smuggled into the handler "just for the demo."

What "done" means before noon

Done is a local GET that returns one note, a POST that returns 405, a contract file that names both results, and a checker that exits zero. A free server is not part of done. It is a later choice, used only if the local contract still holds.

The samples below are a proposal for that cut. They are not a recorded run from this account, and the note text is illustrative.

The working read path

The process binds to localhost first. Nothing in the handler creates, updates, or deletes a note. Unknown ids return 404. Unknown paths return 404. Writes return 405 even if a client guesses a route that might exist next weekend.

# demo_app.py — proposal, not a recorded service
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json

NOTES = {
    "n1": "One read. No writes. Every skip is written down.",
}

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path.startswith("/notes/"):
            note_id = self.path.rsplit("/", 1)[-1]
            text = NOTES.get(note_id)
            body = {"id": note_id, "text": text}
            status = 200 if text else 404
        else:
            body, status = {"error": "only /notes/{id}"}, 404
        raw = json.dumps(body).encode()
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(raw)))
        self.end_headers()
        self.wfile.write(raw)

    def do_POST(self):
        self.send_error(405, "writes are out of this weekend's cut")

    def do_PUT(self):
        self.send_error(405, "writes are out of this weekend's cut")

    def do_DELETE(self):
        self.send_error(405, "writes are out of this weekend's cut")

if __name__ == "__main__":
    ThreadingHTTPServer(("127.0.0.1", 8765), Handler).serve_forever()
Enter fullscreen mode Exit fullscreen mode

The local check is a plan, not a published timing result.

python3 demo_app.py
curl -sS http://127.0.0.1:8765/notes/n1
curl -sS -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8765/notes/n1
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8765/notes/missing
Enter fullscreen mode Exit fullscreen mode

The expected shape is simple. The first request returns the note. The second returns 405. The third returns 404. If any of those differ, the weekend stops. No extra route is added to make the demo feel larger.

A route contract the checker can fail

A contract file sits beside the app. It is the source the checker trusts, not the README. If the file and the handler disagree, the file wins and the process does not get a public bind.

# demo_contract.yaml — illustrative flat file
listen: 127.0.0.1
port: 8765
allowed_methods: ["GET"]
allowed_paths: ["/notes/{id}"]
model_calls_max: 1
tools_effect: read
skip_ledger: skips.md
Enter fullscreen mode Exit fullscreen mode
# check_contract.py — proposal, flat keys only
import sys
from pathlib import Path

REQUIRED = ("allowed_methods", "allowed_paths", "tools_effect", "skip_ledger", "listen")
WRITE_MARKERS = ("POST", "PUT", "PATCH", "DELETE")

def load_flat(path: Path) -> dict:
    data = {}
    for line in path.read_text().splitlines():
        if not line or line.startswith("#") or line.startswith(" "):
            continue
        if ":" not in line:
            continue
        key, value = line.split(":", 1)
        data[key.strip()] = value.strip()
    return data

def main(path: str) -> int:
    contract_path = Path(path)
    data = load_flat(contract_path)
    missing = [key for key in REQUIRED if key not in data]
    if missing:
        print("missing:", ", ".join(missing))
        return 1
    methods = data["allowed_methods"].upper()
    if any(marker in methods for marker in WRITE_MARKERS):
        print("write method is outside the weekend cut")
        return 1
    if data["tools_effect"] != "read":
        print("tool effect must stay read")
        return 1
    if data["listen"] != "127.0.0.1":
        print("listen must stay on localhost until the local check passes")
        return 1
    ledger = contract_path.parent / data["skip_ledger"]
    if not ledger.is_file() or not ledger.read_text().strip():
        print("skip ledger missing or empty")
        return 1
    print("contract ok")
    return 0

if __name__ == "__main__":
    target = sys.argv[1] if len(sys.argv) > 1 else "demo_contract.yaml"
    sys.exit(main(target))
Enter fullscreen mode Exit fullscreen mode
printf "- No note creation: a write needs validation and undo.\n" > skips.md
python3 check_contract.py demo_contract.yaml
Enter fullscreen mode Exit fullscreen mode

Why the parser stays tiny

The loader ignores indented lines and does not understand nested YAML. That is a limitation, not a hidden feature. If the contract grows nested keys, this checker will pass a file it did not actually read. The next change, if one is earned, is a real parser plus a fixture file. Until then the contract stays flat, and the limitation stays in the build log.

Decision table for the weekend

Choice This weekend Left out Reason
GET /notes/{id} Keep The walkthrough has a start and an end
One summary of that note Optional Shows model access without a tool loop
Create or edit notes Skip A write needs validation, storage, and undo
Accounts on a public host Skip A free server demo is not an identity system
Live secrets in the tree Skip The demo must run without production credentials
Latency table Skip This proposal has no measured timings

The table is the review surface. A teammate can reject a row without reading the handler. If a row cannot name a reason, it does not belong in the cut.

Where free model access and a free server fit

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

MonkeyCode enters this method in one place. A side project can use its free model access and its free server option after the localhost contract already passes. Those two availability claims are the only product facts this log uses. Model names, token quotas, hardware size, and how long a free option lasts are not assumed here. They change, and a weekend plan that hard-codes them will go stale before the demo is shown.

The sequence stays ordered.

  1. check_contract.py exits zero on a clean tree.
  2. The GET and the rejected POST both match the expected status codes on 127.0.0.1.
  3. Only then is a free server considered.
  4. The same contract travels with the process. Spare capacity is not a reason to add a write route.
  5. The single model call uses whatever free model access the current docs list. If that list is empty, the note lookup still ships.

The lookup is the demo. The summary is color. A missing model must not expand the tool list, and a free host must not become the place where an untested write is tried "because the machine is already up."

Before any bind past localhost, the operator reads the current project documentation and stops if the documented limits do not cover a one-call read. No quota figure in a chat thread replaces that check.

What the skip ledger holds

The ledger is a file in the tree, not a memory of what felt hard on Saturday.

  • Note creation stays out, because a write path needs validation and a rollback story.
  • A tool loop stays out, because a second call hides cost and makes the walkthrough non-deterministic.
  • Login stays out, because accounts on a public free server are a different product.
  • A benchmark table stays out, because this proposal was not timed.
  • Any claim that a free option is permanent stays out, because permanence was not verified.

Skipped work is still work. Naming it keeps the next weekend from pretending the cut was an accident.

# skips.md
- No create/update/delete: needs storage and undo.
- No second model call: one summary is the ceiling.
- No accounts: public hosting is not an identity system.
- No quota or hardware claims: confirm current docs instead.
Enter fullscreen mode Exit fullscreen mode

Who should not use this cut

This approach is a bad fit when the side project is the write. A form that must save data, a bot that must open a pull request, or a demo whose point is multi-step tool use will be distorted by a read-only contract. Forcing those projects through this checker produces a demo that cannot show the actual behavior.

Teams that need audited production controls should not treat the script as a security review. It does not parse full YAML, does not scan dependencies, and does not prove that a model will refuse a harmful request. It only checks a flat contract and an empty-or-missing ledger.

Anyone pasting live credentials into the demo to see whether a free server responds is outside the method. The read path is designed to run without production tokens. A secret that appears in the tree has already failed the weekend, even if the HTTP status codes look right.

Close the weekend on the contract

The weekend ends when one GET works, one write is rejected, the skip ledger is non-empty, and the checker exits zero. Hosting and model access remain optional layers on top of that result. Readers who want to try the same cut can review MonkeyCode's current free model access and free server option, then confirm the docs before binding anything past localhost.

Top comments (0)