Weekend Build Log: Stdout Is the Demo, Not a UI 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. You sit down on Saturday with one goal. You want a tiny status reporter for later. The coding agent already has a full plan. It wants a dashboard, a worker, and Redis. You did not ask for that extra surface. You asked for a green or red result. This log shows the cut you should make. The repo is a quiet side project. It holds one script and a data folder. You type a loose prompt anyway. "Make a status page I can demo." The agent treats "page" as a product. That wording is the first real leak. "Page" invites HTML, CSS, and routes. "Demo" invites extra moving parts fast. You stop the run before files land. You rewrite the ask as a hard contract. Stdout will be the entire weekend demo. You need one command that exits cleanly. You need a JSON blob on stdout only. You need a non-zero exit on failure. You do not need a browser tab today. You do not need a bound network port. You do not need a login form either. Write the contract before any new code. Keep it small enough to read aloud. If you cannot recite it, cut more scope. Save this file as STATUS CONTRACT.md . Commit it before the agent touches code. Status contract weekend freeze Demo command: make status Stdout: one JSON object, no mixed logs. Exit 0 if the check passes. Exit 1 if the check fails. Required keys: ok: boolean checked at: ISO-8601 string source: string detail: string Forbidden this weekend: HTTP servers HTML templates extra dependencies background workers new config files This file is the only weekend gate. The agent may edit status.py after that. It may also edit the Makefile target. It may not add a second surface. Point at this file when plans grow. Do not argue from memory or vibes. Follow these steps in this exact order. Do not skip ahead to a status UI. make status and nothing else. OUT OF SCOPE.md . The order matters more than raw speed. A failing test keeps the demo honest. An out-of-scope list keeps prompts honest. git add STATUS CONTRACT.md git commit -m "freeze weekend status contract" Commit before the agent writes any code. A frozen file is cheaper than a debate. You can quote it when scope creeps back. This test is a labeled proposal only. Run it on your machine before you trust it. Do not treat the snippet as measured proof. python test status.py import json import subprocess import unittest class StatusStdoutTests unittest.TestCase : def test make status prints one object self : proc = subprocess.run "make", "status" , check=False, capture output=True, text=True, self.assertNotIn "Traceback", proc.stderr payload = json.loads proc.stdout self.assertIn proc.returncode, 0, 1 self.assertIsInstance payload "ok" , bool self.assertEqual set payload , {"ok", "checked at", "source", "detail"}, if name == " main ": unittest.main The test talks to Make, not a server. That choice blocks a hidden local port. If stdout is dirty, json.loads fails. Keep the checker in one Python file. Use the standard library and nothing else. Print JSON, then exit with a status. python status.py from future import annotations import json import sys from datetime import datetime, timezone from pathlib import Path DATA = Path "data" def main - int: exists = DATA.exists and DATA.is dir payload = { "ok": exists, "checked at": datetime.now timezone.utc .isoformat , "source": "data/", "detail": "data dir present" if exists else "data dir missing", } json.dump payload, sys.stdout sys.stdout.write "\n" return 0 if exists else 1 if name == " main ": raise SystemExit main The check is deliberately boring on purpose. A missing folder is enough for a demo. You can swap the predicate next weekend. .PHONY: status test status: python3 status.py test: python3 test status.py Do not add a serve target this weekend. Do not add a docker target this weekend. Do not add a dev target that boots UI. mkdir -p data make status echo $? make test You should see one JSON object only. You should see exit code zero after that. You should see the unit test pass cleanly. Now break the check on purpose. rmdir data make status echo $? You should see "ok": false in stdout. You should see exit code one from Make. That red path is part of the demo. The agent will try to be extra helpful. It will print Running status check... . That single line breaks the JSON parse. Treat dirty stdout as a failed demo. Do not parse "the last line only". The contract says one object, nothing else. Debug with this exact command sequence. make status | cat -A and inspect marks. json.loads against the full stdout. python make status 2 /tmp/status.err | python3 -c "import sys,json; json.load sys.stdin ; print 'clean' " If that pipeline errors, stop adding features. Fix the script before you touch scope again. Silence on stdout is part of the interface. Do not hide the extra ideas in chat. Write them down so they cannot sneak back. OUT OF SCOPE.md - React status board - websocket live feed - Redis and a worker process - FastAPI health route - Docker Compose stack - auth tokens and API keys Read that list before the next agent prompt. If an item is absent from the contract, refuse it. You can reopen the list on a later weekend. Use this table before you accept any plan. If the plan needs a new column, stop cold. | Prompt smell | Likely extra surface | Cut back to | |---|---|---| | "status page" | HTML, CSS, routes | make status | | "live updates" | websockets, a queue | one JSON snapshot | | "so we can share it" | auth, deploy, DNS | stdout in the terminal | | "just a small API" | framework, CORS | a function and exit codes | | "add logging later" | config files, sinks | empty stderr on success | The table is the real weekend tool. Code is only the proof of the cut. Plans that ignore the table waste Saturday. A weekend agent is useful for glue code. It is poor at protecting product scope. You have to protect that scope yourself. Disclosure: This article was prepared as part of MonkeyCode's product outreach. If 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. Keep the product off the critical path. The demo must work with plain Python three. Remove the agent and these steps still hold. This demo proves a checker, not a product. It does not watch production traffic at all. It does not measure model quality either. JSON on stdout is easy to break later. One debug print fails the whole contract. That brittleness is useful for one weekend. The data-folder check is only a stand-in. Replace it with a real predicate later on. Do not expand the command surface with it. Free model access and a free server can change. Do not build a launch plan on those options. Do not treat them as a capacity promise. Do not use this if you need a public URL today. Do not use this if auditors require HTTP health. Do not use this if your audience cannot run Make. Teams with an existing health endpoint should keep it. This log is for a side project with no users. It is a scope knife, not a platform design. You asked for a demo you can actually show. You shipped one command and a frozen contract. You skipped the board the agent wanted first. That is a successful Saturday side project. The JSON blob is enough to screenshot later. Next weekend can earn a second surface, maybe.