{"slug": "weekend-build-log-stdout-is-the-demo-not-a-ui", "title": "Weekend Build Log: Stdout Is the Demo, Not a UI", "summary": "A developer documented a weekend workflow for constraining coding agents to a minimal stdout-based status reporter instead of letting them build an unrequested dashboard, worker, and Redis setup. The approach freezes a written contract (STATUS_CONTRACT.md) specifying a single `make status` command that emits one JSON object and exits 0 or 1, plus an out-of-scope list and a unittest that invokes Make rather than a server. The author argues that committing the contract before the agent writes code prevents scope creep and keeps the demo honest.", "body_md": "You sit down on Saturday with one goal.\n\nYou want a tiny status reporter for later.\n\nThe coding agent already has a full plan.\n\nIt wants a dashboard, a worker, and Redis.\n\nYou did not ask for that extra surface.\n\nYou asked for a green or red result.\n\nThis log shows the cut you should make.\n\nThe repo is a quiet side project.\n\nIt holds one script and a data folder.\n\nYou type a loose prompt anyway.\n\n\"Make a status page I can demo.\"\n\nThe agent treats \"page\" as a product.\n\nThat wording is the first real leak.\n\n\"Page\" invites HTML, CSS, and routes.\n\n\"Demo\" invites extra moving parts fast.\n\nYou stop the run before files land.\n\nYou rewrite the ask as a hard contract.\n\nStdout will be the entire weekend demo.\n\nYou need one command that exits cleanly.\n\nYou need a JSON blob on stdout only.\n\nYou need a non-zero exit on failure.\n\nYou do not need a browser tab today.\n\nYou do not need a bound network port.\n\nYou do not need a login form either.\n\nWrite the contract before any new code.\n\nKeep it small enough to read aloud.\n\nIf you cannot recite it, cut more scope.\n\nSave this file as `STATUS_CONTRACT.md`.\n\nCommit it before the agent touches code.\n\n```\n# Status contract (weekend freeze)\n\nDemo command:\n  make status\n\nStdout: one JSON object, no mixed logs.\nExit 0 if the check passes.\nExit 1 if the check fails.\n\nRequired keys:\n  ok: boolean\n  checked_at: ISO-8601 string\n  source: string\n  detail: string\n\nForbidden this weekend:\n  HTTP servers\n  HTML templates\n  extra dependencies\n  background workers\n  new config files\n```\n\nThis file is the only weekend gate.\n\nThe agent may edit `status.py` after that.\n\nIt may also edit the `Makefile` target.\n\nIt may not add a second surface.\n\nPoint at this file when plans grow.\n\nDo not argue from memory or vibes.\n\nFollow these steps in this exact order.\n\nDo not skip ahead to a status UI.\n\n`make status` and nothing else.`OUT_OF_SCOPE.md`.\nThe order matters more than raw speed.\n\nA failing test keeps the demo honest.\n\nAn out-of-scope list keeps prompts honest.\n\n```\ngit add STATUS_CONTRACT.md\ngit commit -m \"freeze weekend status contract\"\n```\n\nCommit before the agent writes any code.\n\nA frozen file is cheaper than a debate.\n\nYou can quote it when scope creeps back.\n\nThis test is a labeled proposal only.\n\nRun it on your machine before you trust it.\n\nDo not treat the snippet as measured proof.\n\n``` python\n# test_status.py\nimport json\nimport subprocess\nimport unittest\n\nclass StatusStdoutTests(unittest.TestCase):\n    def test_make_status_prints_one_object(self):\n        proc = subprocess.run(\n            [\"make\", \"status\"],\n            check=False,\n            capture_output=True,\n            text=True,\n        )\n        self.assertNotIn(\"Traceback\", proc.stderr)\n        payload = json.loads(proc.stdout)\n        self.assertIn(proc.returncode, (0, 1))\n        self.assertIsInstance(payload[\"ok\"], bool)\n        self.assertEqual(\n            set(payload),\n            {\"ok\", \"checked_at\", \"source\", \"detail\"},\n        )\n\nif __name__ == \"__main__\":\n    unittest.main()\n```\n\nThe test talks to Make, not a server.\n\nThat choice blocks a hidden local port.\n\nIf stdout is dirty, `json.loads` fails.\n\nKeep the checker in one Python file.\n\nUse the standard library and nothing else.\n\nPrint JSON, then exit with a status.\n\n``` python\n# status.py\nfrom __future__ import annotations\n\nimport json\nimport sys\nfrom datetime import datetime, timezone\nfrom pathlib import Path\n\nDATA = Path(\"data\")\n\ndef main() -> int:\n    exists = DATA.exists() and DATA.is_dir()\n    payload = {\n        \"ok\": exists,\n        \"checked_at\": datetime.now(timezone.utc).isoformat(),\n        \"source\": \"data/\",\n        \"detail\": \"data dir present\" if exists else \"data dir missing\",\n    }\n    json.dump(payload, sys.stdout)\n    sys.stdout.write(\"\\n\")\n    return 0 if exists else 1\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n```\n\nThe check is deliberately boring on purpose.\n\nA missing folder is enough for a demo.\n\nYou can swap the predicate next weekend.\n\n```\n.PHONY: status test\n\nstatus:\n    python3 status.py\n\ntest:\n    python3 test_status.py\n```\n\nDo not add a `serve` target this weekend.\n\nDo not add a `docker` target this weekend.\n\nDo not add a `dev` target that boots UI.\n\n```\nmkdir -p data\nmake status\necho $?\nmake test\n```\n\nYou should see one JSON object only.\n\nYou should see exit code zero after that.\n\nYou should see the unit test pass cleanly.\n\nNow break the check on purpose.\n\n```\nrmdir data\nmake status\necho $?\n```\n\nYou should see `\"ok\": false` in stdout.\n\nYou should see exit code one from Make.\n\nThat red path is part of the demo.\n\nThe agent will try to be extra helpful.\n\nIt will print `Running status check...`.\n\nThat single line breaks the JSON parse.\n\nTreat dirty stdout as a failed demo.\n\nDo not parse \"the last line only\".\n\nThe contract says one object, nothing else.\n\nDebug with this exact command sequence.\n\n`make status | cat -A` and inspect marks.`json.loads` against the full stdout.\n\n``` python\nmake status 2>/tmp/status.err | python3 -c \"import sys,json; json.load(sys.stdin); print('clean')\"\n```\n\nIf that pipeline errors, stop adding features.\n\nFix the script before you touch scope again.\n\nSilence on stdout is part of the interface.\n\nDo not hide the extra ideas in chat.\n\nWrite them down so they cannot sneak back.\n\n```\n# OUT_OF_SCOPE.md\n\n- React status board\n- websocket live feed\n- Redis and a worker process\n- FastAPI health route\n- Docker Compose stack\n- auth tokens and API keys\n```\n\nRead that list before the next agent prompt.\n\nIf an item is absent from the contract, refuse it.\n\nYou can reopen the list on a later weekend.\n\nUse this table before you accept any plan.\n\nIf the plan needs a new column, stop cold.\n\n| Prompt smell | Likely extra surface | Cut back to | \n|---|---|---|\n| \"status page\" | HTML, CSS, routes | `make status` | \n| \"live updates\" | websockets, a queue | one JSON snapshot | \n| \"so we can share it\" | auth, deploy, DNS | stdout in the terminal | \n| \"just a small API\" | framework, CORS | a function and exit codes | \n| \"add logging later\" | config files, sinks | empty stderr on success | \n\nThe table is the real weekend tool.\n\nCode is only the proof of the cut.\n\nPlans that ignore the table waste Saturday.\n\nA weekend agent is useful for glue code.\n\nIt is poor at protecting product scope.\n\nYou have to protect that scope yourself.\n\nDisclosure: This article was prepared as part of MonkeyCode's product outreach.\n\nIf you want model access without a paid key, MonkeyCode's free model access can run this same constrained loop. If you do not want the laptop as the only host, the free server option can hold the tiny repo. Neither one replaces `STATUS_CONTRACT.md`. Paste that file before the first prompt if you try it there.\n\nKeep the product off the critical path.\n\nThe demo must work with plain Python three.\n\nRemove the agent and these steps still hold.\n\nThis demo proves a checker, not a product.\n\nIt does not watch production traffic at all.\n\nIt does not measure model quality either.\n\nJSON on stdout is easy to break later.\n\nOne debug print fails the whole contract.\n\nThat brittleness is useful for one weekend.\n\nThe data-folder check is only a stand-in.\n\nReplace it with a real predicate later on.\n\nDo not expand the command surface with it.\n\nFree model access and a free server can change.\n\nDo not build a launch plan on those options.\n\nDo not treat them as a capacity promise.\n\nDo not use this if you need a public URL today.\n\nDo not use this if auditors require HTTP health.\n\nDo not use this if your audience cannot run Make.\n\nTeams with an existing health endpoint should keep it.\n\nThis log is for a side project with no users.\n\nIt is a scope knife, not a platform design.\n\nYou asked for a demo you can actually show.\n\nYou shipped one command and a frozen contract.\n\nYou skipped the board the agent wanted first.\n\nThat is a successful Saturday side project.\n\nThe JSON blob is enough to screenshot later.\n\nNext weekend can earn a second surface, maybe.", "url": "https://wpnews.pro/news/weekend-build-log-stdout-is-the-demo-not-a-ui", "canonical_source": "https://dev.to/hackgo_6978/weekend-build-log-stdout-is-the-demo-not-a-ui-1k9h", "published_at": "2026-09-11 15:19:52+00:00", "updated_at": "2026-09-11 15:43:53.276736+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Redis", "Python", "Make", "unittest"], "alternates": {"html": "https://wpnews.pro/news/weekend-build-log-stdout-is-the-demo-not-a-ui", "markdown": "https://wpnews.pro/news/weekend-build-log-stdout-is-the-demo-not-a-ui.md", "text": "https://wpnews.pro/news/weekend-build-log-stdout-is-the-demo-not-a-ui.txt", "jsonld": "https://wpnews.pro/news/weekend-build-log-stdout-is-the-demo-not-a-ui.jsonld"}}