Weekend Build Log: The Demo Writes One JSONL Line
You open the laptop at nine on Saturday. The idea is an AI classifier for bug reports. You want a chat box and a clever model.
By noon you have a page that looks finished. You cannot replay what the model actually said. Sunday you will not remember the original prompt.
That Saturday page is not a real demo. It is theater with a glowing text box.
This weekend you will cut that path. The demo is one transcript file on disk. One JSONL line proves the classifier ran.
The chat UI waits for another weekend. You will not paint a box around missing proof.
The weekend rule
You ship a classifier with a paper trail. The model stays optional until that trail works.
Live model calls come last, not first. You keep the weekend scope small and brutal.
You allow one verb, one fixture, one live ping. Everything else belongs in a written skip list.
What you will actually ship
You will ship four files and one command. The command appends exactly one transcript line. That line is the only demo you give.
-
prompt.txtholds the frozen instruction for every run. -
fixture.jsonholds one known bug report only. -
SKIPPED.mdlists what you refused to build. -
classify.pyreplays, pings once, and appends JSONL.
Step 1: Name the one verb
You do not build an open-ended AI assistant. You classify one bug report this weekend. The output is one label and one reason.
Keep the label set tiny on purpose. Allow only bug, docs, or unknown labels. The reason must stay one short sentence.
Write the contract on disk before any model call.
{
"label": "bug",
"reason": "Stack trace in the report body."
}
Reject extra JSON keys without any debate. The transcript must record that schema rejection. Do not clean up the payload by hand.
Step 2: Freeze the prompt on disk
Do not type prompts into a throwaway chat box. Keep the full instruction in prompt.txt only. Hash the file before every recorded run.
Classify the bug report.
Return JSON with keys label and reason.
label must be bug, docs, or unknown.
reason must be one sentence.
Do not invent extra keys.
shasum -a 256 prompt.txt
Store that hash inside each transcript line. Old lines become invalid if the hash changes. Do not mix prompt versions in one JSONL file.
Step 3: Write the skip list first
Write SKIPPED.md before you write any Python. This file is part of the demo itself. Print it at the end of every run.
# Skipped this weekend
- No chat UI
- No user accounts
- No streaming tokens
- No retry storms
- No vector store
- No dashboard
- No mobile layout
You will want a UI around hour two. You will not add it this weekend. The skip list is the real product constraint.
Step 4: Replay a fixture before any live call
Live answers can hide last hour's failure. A fixture keeps the failure in view. Store one bug report you already understand.
{
"id": "rpt-001",
"title": "Null pointer in checkout",
"body": "TypeError: Cannot read properties of null (reading 'id')\n at checkout.js:41"
}
The offline path uses boring keyword rules. It is not intelligence and does not pretend. You must pass this path before a live call.
# classify.py — local example, not a vendor SDK
from __future__ import annotations
import hashlib
import json
import os
import sys
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).parent
PROMPT = (ROOT / "prompt.txt").read_text(encoding="utf-8")
PROMPT_HASH = hashlib.sha256(PROMPT.encode()).hexdigest()
TRANSCRIPT = ROOT / "transcripts.jsonl"
def rule_classify(report: dict) -> dict:
text = f"{report['title']}\n{report['body']}".lower()
if "typeerror" in text or "null" in text:
return {
"label": "bug",
"reason": "Stack trace in the report body.",
}
if "typo" in text or "readme" in text:
return {
"label": "docs",
"reason": "The report points at documentation text.",
}
return {
"label": "unknown",
"reason": "No stack trace or docs cue found.",
}
def valid_payload(row: dict) -> bool:
if set(row.keys()) != {"label", "reason"}:
return False
if row["label"] not in {"bug", "docs", "unknown"}:
return False
if not isinstance(row["reason"], str):
return False
words = row["reason"].split()
if not words or len(words) > 14:
return False
return True
This core uses no network and no hidden state. You can demo it on a train ride. That is the point of Saturday morning.
Step 5: Append one transcript line
A green printout is not the real demo. The demo is a JSONL append you can cat. Each transcript line must stand alone later.
def append_transcript(row: dict) -> None:
with TRANSCRIPT.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(row, ensure_ascii=False) + "\n")
def build_row(report: dict, result: dict, source: str, ok: bool) -> dict:
return {
"ts": datetime.now(timezone.utc).isoformat(),
"report_id": report["id"],
"prompt_hash": PROMPT_HASH,
"source": source,
"ok": ok,
"result": result,
}
The source field is fixture or live only. ok is a schema check, not a vibe. Never mark a row ok because it looked clever.
Step 6: One live ping, then stop
Wait until the fixture row is ok. Then try the live path at most once. Do not loop the live call at all.
Do not retry the live call five times. Use one HTTP request and one timeout. Then stop and file the live result.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. Need a live ping after the fixture passes? MonkeyCode offers free model access and a free server option.
Point the script at a base URL you control. Treat the path below as an example only. Adapt the request body to your server.
def live_classify(report: dict, timeout_s: float = 20.0) -> dict:
base = os.environ.get("MODEL_BASE_URL", "").rstrip("/")
if not base:
raise RuntimeError("MODEL_BASE_URL is not set")
payload = json.dumps({
"prompt": PROMPT,
"report": report,
}).encode("utf-8")
req = urllib.request.Request(
url=f"{base}/classify",
data=payload,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=timeout_s) as resp:
body = json.loads(resp.read().decode("utf-8"))
if not valid_payload(body):
raise ValueError("schema rejected")
return body
Keep that HTTP timeout tight on purpose. A hung call is a failed weekend demo. File the error instead of waiting around.
Wire the command path in main next. Keep fixture logic above the live branch. Exit non-zero on schema or network failure.
def main() -> int:
fixture = json.loads((ROOT / "fixture.json").read_text(encoding="utf-8"))
skipped = (ROOT / "SKIPPED.md").read_text(encoding="utf-8")
offline = rule_classify(fixture)
offline_ok = valid_payload(offline)
append_transcript(build_row(fixture, offline, "fixture", offline_ok))
print("fixture", "ok" if offline_ok else "FAIL", offline)
if not offline_ok:
print(skipped)
return 1
if "--live" not in sys.argv:
print("live skipped; pass --live for one ping")
print(skipped)
return 0
try:
live = live_classify(fixture)
append_transcript(build_row(fixture, live, "live", True))
print("live ok", live)
except Exception as err:
append_transcript(
build_row(fixture, {"error": str(err)}, "live", False)
)
print("live FAIL", err)
print(skipped)
return 1
print(skipped)
return 0
if __name__ == "__main__":
raise SystemExit(main())
Run the fixture path first every time. You should see one new line in transcripts.jsonl. Open that line and read it aloud.
That spoken JSONL line is the demo. Do not skip reading the raw file.
python3 classify.py
Only then try live against your server. If live fails, you still have the fixture line. The weekend artifact is still not empty.
Do not delete JSONL because live looked ugly. Ugly live rows are still useful evidence.
export MODEL_BASE_URL="http://127.0.0.1:8080"
python3 classify.py --live
The demo gate
Do not screenshot a chat window as proof. Cat the last transcript line instead of that. Then print the skip list to stdout.
tail -n 1 transcripts.jsonl | python3 -m json.tool
cat SKIPPED.md
Ask one question before you call it done. Can a stranger replay this without your voice? If they need your memory, you failed the gate.
A tiny decision table
Use this table when you feel tempted to add features.
| Gate | Pass | Fail next action |
|---|---|---|
prompt hash matches prompt.txt
|
append the row | start a new JSONL file |
| fixture schema is valid | allow --live
|
stop, no live call |
| live timeout returns JSON | append source=live
|
append error, stop |
| extra keys in the payload | reject the row | keep the fixture row |
| skip list still true | ship the command | delete the extra feature |
Read the fail column twice before coding. Most weekend bloat dies in that column. You do not need a fifth library.
What you skipped, and why
You skipped the UI because UIs hide the contract. You skipped retries because retries hide flaky servers. You skipped embeddings because they start a second product.
You skipped streaming because a stream is hard to file. A JSON object files cleanly on local disk. You skipped auth because nobody else has this repo yet.
The skip list is not a confession of shame. It is how you finish on Sunday. Keep it in the demo script output.
Limitations
This workflow will not train a model. It will not rank any model quality. It will not replace a real eval suite.
The rule classifier is a stub on purpose. It only exists to prove the pipe. Do not ship those keywords as intelligence.
The live POST uses a single timeout. It will fail on slow home networks. That failure is useful for this weekend.
Do not wrap it in retries yet. Transcript files also grow without a bound. This pattern is for a weekend, not a platform.
Prompt hashes do not prove the server used your prompt. They only prove what you sent from disk. Believe the local transcript, not a landing page.
Who should not use this
Do not use this if you need a public chatbot. Do not use this for production incidents. Do not use this as a vendor benchmark.
Do not use this if your reports are private. JSONL on disk is not a vault. Keep every fixture synthetic and very dull.
Do not use this if you already have an eval harness. You would be stepping backward on purpose. Use that harness instead of this script.
Sunday check
You should have one frozen prompt file. You should have one fixture row that passes. You may have one live row beside it.
You should still respect the skip list. If you added a UI, you broke the rule. Delete the UI and keep the JSONL.
The model did not do your engineering work. You did the scoping and the filing. The transcript is the proof you can show.
Top comments (0)