The first fifteen minutes of a free AI coding session usually fail on invented context, not models. Your agent does not crash; it politely guesses the package manager, the test command, and the bind address. Those guesses feel helpful until you spend the rest of the hour undoing a repository that never existed. I now refuse to start a session until a tiny contract file and a local gate both pass.
I still think that warning sounds dramatic for a missing lockfile and a guessed runner. Think of a taxi that leaves before you confirm the street, then bills you for scenery. Free models make the ride cheap, and a free server makes the wrong street look like real progress. The developer-experience wound is not latency; it is confident motion inside a directory nobody verified.
I keep watching the same opening act whenever a fresh coding agent meets a brownfield folder. The tool greets me, scans a handful of files, and starts scaffolding as if this repo were a tutorial. npm appears where I already standardized on pnpm, and Jest appears where I run node:test today. Fifteen minutes later I own two lockfiles, a health check on the wrong port, and a mystery commit.
The one fix that actually mattered was small enough to feel almost insulting at first. I stopped asking the agent to be careful and started requiring a committed session contract plus a gate. If the contract is missing, or the working directory is not the named git root, nothing starts. The agent can still write code after that checkpoint; it just cannot invent the room around it.
Here is the contract I drop at the repo root under the name session.contract.json for every serious branch. Treat the file as a proposed artifact you can copy, not as a benchmark from some private bake-off. It is deliberately boring, because boredom is harder for an agent to “improve.”
{
"name": "billing-api",
"git_root_basename": "billing-api",
"package_manager": "pnpm",
"lockfile": "pnpm-lock.yaml",
"test_command": "pnpm test -- --run",
"typecheck_command": "pnpm exec tsc --noEmit",
"server": {
"bind": "127.0.0.1",
"port": 8787,
"health_path": "/healthz",
"start_command": "pnpm dev"
},
"forbidden_inferences": [
"create docker-compose.yml",
"add a second package manager",
"bind 0.0.0.0",
"rewrite the test runner"
]
}
Why JSON instead of a chatty README that an agent can keep writing like a sequel? A README is a novel, and most agents love novels they are invited to continue without asking. A contract is a door lock you can hash, diff, and fail closed before the first generated patch. I still write human notes elsewhere; I just refuse to let prose be the only source of truth.
The gate lives in scripts/ai-session-gate.sh and I run it locally before any prompt leaves the editor. Consider the script proposed and copy-pasteable, because I am not selling a measured pass rate tonight. It should fail closed on a missing git root, a missing contract, or a folder whose basename does not match.
#!/usr/bin/env bash
set -euo pipefail
CONTRACT="${1:-session.contract.json}"
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [[ -z "${ROOT}" ]]; then
echo "gate: not inside a git work tree" >&2
exit 10
fi
cd "${ROOT}"
if [[ ! -f "${CONTRACT}" ]]; then
echo "gate: missing ${CONTRACT}; refusing to start the session" >&2
exit 11
fi
command -v python3 >/dev/null || { echo "gate: python3 required" >&2; exit 12; }
python3 - "${CONTRACT}" <<'PY'
import json, sys, pathlib
path = sys.argv[1]
data = json.loads(pathlib.Path(path).read_text())
root = pathlib.Path.cwd()
basename = data["git_root_basename"]
if root.name != basename:
print(f"gate: cwd basename {root.name!r} != {basename!r}", file=sys.stderr)
sys.exit(13)
lockfile = data["lockfile"]
if not (root / lockfile).is_file():
print(f"gate: lockfile {lockfile} is missing", file=sys.stderr)
sys.exit(14)
pm = data["package_manager"]
server = data["server"]
print(f"gate: ok root={root} pm={pm} lockfile={lockfile}")
print(f"gate: test={data['test_command']}")
print(f"gate: server={server['bind']}:{server['port']}{server['health_path']}")
PY
echo "gate: session may start; paste the contract into the first message"
Make it executable once, then run it from anywhere inside the work tree and let it cd for you. A healthy pass looks almost boring, and that boredom is the entire point of the ritual. A useful failure names the basename mismatch and refuses to flatter you with a partial green check.
chmod +x scripts/ai-session-gate.sh
./scripts/ai-session-gate.sh
gate: ok root=/Users/you/src/billing-api pm=pnpm lockfile=pnpm-lock.yaml
gate: test=pnpm test -- --run
gate: server=127.0.0.1:8787/healthz
gate: session may start; paste the contract into the first message
gate: cwd basename 'billing-api-copy' != 'billing-api'
Has a copied folder with a hyphenated suffix ever wasted your evening while the agent kept coding happily? The first prompt after a green gate is not a feature request; it is a short, rude recitation. I paste the JSON and require the agent to echo bind address, lockfile, and test command before edits. If it cannot quote those three facts, I stop, because a junior would not get a looser bar.
Would you merge a pull request from someone who could not name the test runner in standup? Then why let a free model do that with more confidence, nicer prose, and much less shame? When a free server joins the story, I add a second check that never talks to the model at all. The process that claims to be my app must answer on the contracted loopback port, not in chat.
#!/usr/bin/env bash
set -euo pipefail
BIND="${1:-127.0.0.1}"
PORT="${2:-8787}"
PATH_HEALTH="${3:-/healthz}"
if curl -fsS --max-time 2 "http://${BIND}:${PORT}${PATH_HEALTH}" >/dev/null; then
echo "health: ok ${BIND}:${PORT}${PATH_HEALTH}"
exit 0
fi
echo "health: no response from ${BIND}:${PORT}${PATH_HEALTH}" >&2
exit 20
This is where MonkeyCode actually participates in the method rather than sitting in a slogan. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I treat it as one place with free model access and a free server option, which makes bad binds obvious. Strip every product name from this article and the gate still earns its keep on a laptop.
Tear the first fifteen minutes down as a timeline and the failure modes stop looking mysterious. Minute one is open or clone, which is exactly when the basename check should fire and halt you. Minutes two through five are dependency reality, so the lockfile belongs in the contract, not in a vibe. Minutes six through fifteen are the helpful Docker file, the second framework, and the server that only exists in transcript.
The contract attacks minute one, and the health curl attacks the fake demo at minute fifteen. Everything between those checks is just an agent filling silence with architecture you did not order. I think of a kitchen ticket that does not inspire the cook so much as prevent last night's special. Free inference is a talented line cook with no ticket printer, and your job is hanging the ticket.
Should you wire the gate into an agent loop on day one, before it is boring on your laptop? I would not; I would keep a proposed npm script so the ritual lives in muscle memory first. After that, the human ritual is two commands you can type when you are only half awake.
{
"scripts": {
"ai:gate": "bash scripts/ai-session-gate.sh",
"ai:health": "bash scripts/ai-health.sh 127.0.0.1 8787 /healthz"
}
}
pnpm ai:gate
# only then start the agent, and only then paste session.contract.json
pnpm ai:health
git diff --exit-code -- session.contract.json
That last command is the quiet one people skip, and skipping it is how the ticket gets reprinted. If git diff --exit-code -- session.contract.json exits nonzero, the session is no longer bound by the rules you hung. Pause and ask why the cook rewrote the order before you taste whatever landed on the plate. The agent can still be useful after that pause; it just lost the right to keep moving on inertia.
Limitations sit in the open because this pattern is a seatbelt, not a substitute for a brain. The contract cannot see secrets an agent later pastes into a helpful log you forgot to read. The health check cannot prove the handler is yours if another process already bound the same port. JSON will not stop a rewrite of the contract unless you commit the file and watch git diff closely.
I am not claiming latency numbers, pass rates, or model names, because this piece is not a bake-off. If your work is air-gapped, safety-critical, or stuck inside a change window, skip remote free servers entirely. Who should skip the whole ritual, not just the remote box, without feeling guilty about the shortcut? People pairing on a twenty-minute kata, or a single file with no lockfile and no server, can skip it.
If you lack a git root you trust, the gate will nag, and you should listen instead of weakening it. The wider conversation keeps celebrating agents that plan, tool-call, and just handle the missing details for you. Handling the missing details is exactly how my accidental Jest suite got born on a pnpm repository. I would rather an agent pause like a senior who asks which package manager is actually real here.
Is that slower than a magical first message that scaffolds three directories before you finish coffee? Yes, by about thirty seconds of JSON and a shell script that exits nonzero without apologizing. Is it slower than rebuilding a tree the agent invented before lunch while sounding perfectly sure of itself? Not even close, which is why I now treat a green gate as the real start of the session.
If you try this, keep the artifact small enough to paste into a pull request description without ceremony. A contract, a gate, a health curl, and a first prompt that quotes them are the whole DX patch. The models can stay free, the server can stay free, and tomorrow you can still discard the session without guilt. What you should not discard is the habit of making the repo answer back before anyone starts coding.
Steal the three files either way, even if you never touch the product I used for the server. If you want a free model plus a free server to try the gate against, MonkeyCode is one option. I will not pretend the JSON cares which vendor answered, so long as port 8787 tells the truth.
Top comments (0)