cd /news/ai-agents/weekend-build-log-stdout-is-the-demo… · home topics ai-agents article
[ARTICLE · art-127016] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

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.

by read6 min views2 publishedSep 11, 2026

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.


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.

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.

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.

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.


- 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.

── more in #ai-agents 4 stories · sorted by recency
── more on @redis 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/weekend-build-log-st…] indexed:0 read:6min 2026-09-11 ·