DEV Community

Charlie Hu
Charlie Hu

Posted on

Stop After the Demo: A Three-File Halt Kit for Weekend AI Builds

Weekend AI side projects rarely die from a missing import. They die because the agent keeps adding features after the demo already runs. A three-file halt kit treats a working demo as a freeze, not a checkpoint.

The kit is a proposed weekend workflow, not a production release process. It is meant for a throwaway side project that has to be showable by Sunday night. Cheap generation makes extra work feel free. The scarce resource is attention, not keystrokes.

The failure mode

An agent that has already produced a running script will still “improve” it. Auth appears. Then a cache. Then a Dockerfile and a second CLI. Each addition is locally reasonable. Together they blow the weekend.

This is adjacent to agentic coding, not a glossary of agent terms. The only behavior that matters here is stopping. When generation is fast, unpublished skips become unpaid debt. The halt kit spends the remaining attention on a freeze condition instead of on another helper function.

The three files

The proposed layout is small on purpose.

halt-kit/
  demo.sh
  skip.yml
  halt_test.py
  Makefile
  src/
    app.py
    fixture.json
  README.md
Enter fullscreen mode Exit fullscreen mode

Nothing else is in the ship set. If the agent wants a docker-compose.yml, the allowlist test fails. That failure is the feature.

1. The demo command

demo.sh is the only definition of “done.” It must finish in a bounded time and print a stable phrase.

#!/usr/bin/env bash
# demo.sh — proposed weekend freeze command
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
cd "$ROOT"

python3 - <<'PY'
import subprocess, sys
from pathlib import Path
try:
    out = subprocess.run(
        [sys.executable, "src/app.py", "--fixture", "src/fixture.json", "--once"],
        check=True, capture_output=True, text=True, timeout=15,
    )
except subprocess.TimeoutExpired:
    sys.stderr.write("demo exceeded 15s\n")
    sys.exit(1)
Path("/tmp/halt-kit-demo.out").write_text(out.stdout, encoding="utf-8")
sys.stdout.write(out.stdout)
if "DEMO_OK inventory=3" not in out.stdout:
    sys.exit(1)
PY
Enter fullscreen mode Exit fullscreen mode

A matching stub app keeps the demo honest. The script is a labeled example, not a measured service.

# src/app.py — labeled example, not a production inventory system
from __future__ import annotations

import argparse
import json
from pathlib import Path


def load_fixture(path: Path) -> list[dict]:
    data = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(data, list):
        raise SystemExit("fixture must be a JSON list")
    return data


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--fixture", required=True)
    parser.add_argument("--once", action="store_true")
    args = parser.parse_args()
    items = load_fixture(Path(args.fixture))
    # Single behavior: count in-stock rows. No cart. No users.
    in_stock = [row for row in items if row.get("qty", 0) > 0]
    print(f"DEMO_OK inventory={len(in_stock)}")


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode
[
  {"sku": "A-1", "qty": 2},
  {"sku": "B-9", "qty": 0},
  {"sku": "C-3", "qty": 1}
]
Enter fullscreen mode Exit fullscreen mode

The demo is deliberately boring. Inventory count from a fixture is enough to prove the loop. A weekend build that needs OAuth is a different project.

2. The skip list

skip.yml is not decoration. The halt test parses it as a deny list and as the ship allowlist.

# skip.yml
demo_phrase: "DEMO_OK inventory=3"
max_demo_seconds: 15
non_goals:
  - authentication
  - database
  - docker
  - ci
  - admin-ui
  - pagination
  - caching
forbidden_path_substrings:
  - docker
  - compose
  - auth
  - oauth
  - prisma
  - next.config
  - .github
allowed_paths:
  - demo.sh
  - skip.yml
  - halt_test.py
  - Makefile
  - src/app.py
  - src/fixture.json
  - README.md
Enter fullscreen mode Exit fullscreen mode

If the agent adds src/auth.py to “just protect the demo,” the path is not allowed. The skip list also gives a human a place to record what was refused. That record is the build-log half of the weekend.

3. The allowlist test

halt_test.py is the reproducible artifact. It fails the freeze when the tree grows, when forbidden names appear, or when the demo phrase drifts.

#!/usr/bin/env python3
# halt_test.py — proposed allowlist + skip-list freeze
from __future__ import annotations

import os
import subprocess
import sys
from pathlib import Path

try:
    import yaml
except ImportError:
    sys.stderr.write("install pyyaml before running halt_test.py\n")
    sys.exit(2)

ROOT = Path(__file__).resolve().parent


def load_skip() -> dict:
    return yaml.safe_load((ROOT / "skip.yml").read_text(encoding="utf-8"))


def tracked_files() -> list[str]:
    git = subprocess.run(
        ["git", "ls-files"],
        cwd=ROOT,
        capture_output=True,
        text=True,
        check=False,
    )
    if git.returncode == 0 and git.stdout.strip():
        return [line for line in git.stdout.splitlines() if line]
    files: list[str] = []
    for dirpath, dirnames, filenames in os.walk(ROOT):
        dirnames[:] = [d for d in dirnames if d not in {".git", "__pycache__", ".venv"}]
        for name in filenames:
            rel = (Path(dirpath) / name).relative_to(ROOT).as_posix()
            files.append(rel)
    return files


def main() -> int:
    cfg = load_skip()
    allowed = set(cfg["allowed_paths"])
    forbidden = tuple(cfg["forbidden_path_substrings"])
    files = tracked_files()

    extra = sorted(set(files) - allowed)
    missing = sorted(allowed - set(files))
    banned = sorted(f for f in files if any(s in f.lower() for s in forbidden))

    errors: list[str] = []
    if extra:
        errors.append("extra paths: " + ", ".join(extra))
    if missing:
        errors.append("missing ship files: " + ", ".join(missing))
    if banned:
        errors.append("forbidden names: " + ", ".join(banned))

    demo = subprocess.run(
        ["bash", str(ROOT / "demo.sh")],
        cwd=ROOT,
        capture_output=True,
        text=True,
    )
    if demo.returncode != 0:
        errors.append(f"demo.sh exited {demo.returncode}: {demo.stderr[-400:]}")

    phrase = cfg["demo_phrase"]
    blob = demo.stdout
    out_file = Path("/tmp/halt-kit-demo.out")
    if out_file.exists():
        blob += out_file.read_text(encoding="utf-8")
    if phrase not in blob:
        errors.append(f"demo phrase missing: {phrase!r}")

    if errors:
        sys.stderr.write("HALT failed\n" + "\n".join(errors) + "\n")
        return 1
    print("HALT ok — demo frozen")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode

A tiny Makefile keeps the commands short.

# Makefile — proposed weekend targets
.PHONY: halt demo

demo:
    bash ./demo.sh

halt:
    python3 halt_test.py
Enter fullscreen mode Exit fullscreen mode

Run order for a clean tree:

python3 -m pip install --user pyyaml
chmod +x demo.sh halt_test.py
make halt
Enter fullscreen mode Exit fullscreen mode

A passing run prints HALT ok — demo frozen. Any new file, any forbidden substring, or a changed demo phrase fails the freeze.

Proposed weekend sequence

The sequence is a build-log template. It is not a claim that a specific repo shipped on a given date.

  1. Write skip.yml first. Name the demo phrase and the non-goals before any generated code exists.
  2. Write src/fixture.json and the smallest src/app.py that can print the phrase.
  3. Write demo.sh and confirm it exits 0 in under 15 seconds.
  4. Write halt_test.py and run it. The first pass should fail on missing files, then pass once the allowlist matches.
  5. Only then point an AI coding session at the folder, with make halt as the acceptance command.
  6. When the test passes, stop. Do not ask for one more improvement.

Off-laptop generation fits this loop. A session that runs on a free remote coding server still needs a local contract, because the model cannot feel the Sunday deadline.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is relevant here only as one place that offers free model access and a free server option for that remote session. The halt kit does not depend on a particular vendor. Any coding agent that can run python3 halt_test.py can sit behind the same freeze. The product claims above are limited to those two availability points. No model names, quotas, or hardware details are asserted here.

A short prompt that respects the kit:

Implement only files listed in skip.yml allowed_paths.
Do not add authentication, Docker, CI, or extra CLI flags.
Stop when `make halt` prints HALT ok.
If a change would add a path, refuse it and list the path instead.
Enter fullscreen mode Exit fullscreen mode

Paste that once. Do not iterate after a green halt.

Agent suggestion versus halt action

Keep this table next to the skip list. It turns a vague “don’t overbuild” note into a repeatable refusal.

  • Add login / JWT: refuse. Authentication is a non-goal. The fixture is not a user.
  • Add Postgres or SQLite: refuse. JSON on disk is the database for the demo.
  • Add Docker Compose: refuse. If the demo needs a container to start, the demo is too big.
  • Add GitHub Actions: refuse. One local command is the gate.
  • Add pagination, caching, or an admin UI: refuse. None of those change the demo phrase.
  • Split app.py into a package with helpers: refuse unless the new files are added to allowed_paths first. Extra paths fail the halt.
  • Change DEMO_OK inventory=3 to a richer JSON payload: refuse. Phrase drift is a failed freeze.
  • Fix a crash that prevents demo.sh from exiting 0: allow, but only inside allowed paths.

The README should copy the skip list in human language. A future session will otherwise restore the cuts and call it polish.

What this weekend skips on purpose

The public record of cuts belongs in the repo, not in chat history.

  • Authentication. A fixture is not a user store.
  • Persistence beyond one JSON file. Durability is out of scope.
  • Containers and cloud deploy scripts. Shipping the demo is not shipping a platform.
  • CI matrices. make halt is the only gate in this workflow.
  • Line-level budgets and token accounting. Those are separate controls. This kit only answers whether the tree and the demo phrase still match the freeze.

The working demo is the inventory phrase printed from src/fixture.json. Everything else is a later project.

Limitations

The allowlist is path-based. An agent can still bloat src/app.py without adding a file. Pairing a line budget with this kit is possible. That pairing is out of scope here.

git ls-files only sees tracked files. Untracked junk can hide until git add -A. The walk fallback is better for a brand-new folder and worse for a dirty home directory. Run the test from the project root only.

The demo timeout lives in Python so macOS does not need GNU timeout. The rest of the kit still assumes bash and python3. A Windows-only tree would need a different demo wrapper.

YAML parsing needs PyYAML. A JSON skip file would drop that dependency. The YAML form is easier to edit by hand during a weekend, which is the trade.

The kit does not measure model quality, token use, or latency. A green halt is not evidence that the code is safe to expose on a network.

Who should not use this

Teams with a real release train should not replace code review with halt_test.py. The script is a weekend freeze, not a security boundary.

Projects that must ship auth, storage, and deploy scripts on day one should not start from this allowlist. Those are product paths, not skipped paths.

People who want a framework tour should pick a tutorial instead. This workflow is hostile to extra files on purpose.

Close

The core conclusion does not change after the files exist. Freeze the demo, publish the skips, and treat extra paths as test failures. The working artifact is the green halt, not a longer backlog.

If a free remote coding session is a useful place to run the agent half of this loop, MonkeyCode's free model access and free server option can host that session. The halt test still runs the same way on a laptop.

Top comments (0)