DEV Community

Riley Zhang
Riley Zhang

Posted on

Weekend Build Log: The Demo Writes One JSONL Line

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.

  1. prompt.txt holds the frozen instruction for every run.
  2. fixture.json holds one known bug report only.
  3. SKIPPED.md lists what you refused to build.
  4. classify.py replays, 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."
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode
shasum -a 256 prompt.txt
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
    }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)