DEV Community

Riley Zhang
Riley Zhang

Posted on

Weekend Build Log: Stdout Is the Demo, Not a UI

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 Saturday scene

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.

What you actually need

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.

Freeze a stdout contract first

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

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.

Numbered build, not a brainstorm

Follow these steps in this exact order.
Do not skip ahead to a status UI.

  1. Freeze the contract in git first.
  2. Add a failing test for stdout JSON.
  3. Write the smallest checker script.
  4. Wire make status and nothing else.
  5. Run the happy path and the miss.
  6. Dump rejected ideas into 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.

Step 1: commit the freeze

git add STATUS_CONTRACT.md
git commit -m "freeze weekend status contract"
Enter fullscreen mode Exit fullscreen mode

Commit before the agent writes any code.
A frozen file is cheaper than a debate.
You can quote it when scope creeps back.

Step 2: add a failing stdout test

This test is a labeled proposal only.
Run it on your machine before you trust it.
Do not treat the snippet as measured proof.

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

The test talks to Make, not a server.
That choice blocks a hidden local port.
If stdout is dirty, json.loads fails.

Step 3: write the smallest script

Keep the checker in one Python file.
Use the standard library and nothing else.
Print JSON, then exit with a status.

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

The check is deliberately boring on purpose.
A missing folder is enough for a demo.
You can swap the predicate next weekend.

Step 4: one Makefile target

.PHONY: status test

status:
    python3 status.py

test:
    python3 test_status.py
Enter fullscreen mode Exit fullscreen mode

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.

Step 5: run it like a demo

mkdir -p data
make status
echo $?
make test
Enter fullscreen mode Exit fullscreen mode

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

You should see "ok": false in stdout.
You should see exit code one from Make.
That red path is part of the demo.

When stdout gets dirty

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.

  1. Run make status | cat -A and inspect marks.
  2. Keep traces on stderr, never on stdout.
  3. Run json.loads against the full stdout.
  4. Fail the test if extra text exists anywhere.
make status 2>/tmp/status.err | python3 -c "import sys,json; json.load(sys.stdin); print('clean')"
Enter fullscreen mode Exit fullscreen mode

If that pipeline errors, stop adding features.
Fix the script before you touch scope again.
Silence on stdout is part of the interface.

Step 6: name what you refused

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

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.

A Saturday decision table

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.

Where a coding agent still helps

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.

Limitations

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.

Who should not use this cut

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.

Close the laptop

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.

Top comments (0)