You opened the free server at 19:40 and called the run done.
The model wrote a short note and named the bug fixed.
Your later CI job failed on a toolchain you never wrote down.
This letter lists the three setup mistakes that cost that day.
The scene is a reconstructed first run, not a benchmark.
No figure below is a measured product result.
The ticket was a retry helper with one failing test.
The intended edit was one source file plus its test.
The tree you merged was wider than that ticket.
A floating image tag sat under the free server session.
A missing path fence let extra files change.
A closing paragraph stood in for a saved diff stat.
Where a hosted loop fits
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode provides free model access and a free server option.
Those two facts are operator-supplied claims for this draft.
This article invents no model names, quotas, hardware, or duration.
The loop still works if you delete every product name.
You need a remote shell you control and a model that edits.
The checks below are the part that decides acceptance.
Keep pin.yml, fence.json, and gate-result.json in the ticket folder.
Keep them out of the chat scroll you will not reread.
Review those files even when the model sounds finished.
Mistake 1: A floating base ate the replay
You started the box from a moving tag named latest.
A later start resolved that tag to a different digest.
The same patch then failed under a different compiler.
You had no pin file beside the ticket.
You could not prove which base produced the note.
Replay became guesswork, and the afternoon went to setup.
Write the digest before the first prompt, not after failure.
Read it from the server you actually started.
If the panel hides the digest, stop and ask.
# proposal pin file — not a captured live digest
schema: remote-pin/v1
image_ref: "example.invalid/devbox:pinned"
image_digest: "sha256:REPLACE_WITH_LIVE_DIGEST"
lockfile: "package-lock.json"
lockfile_sha256: "REPLACE_WITH_LOCK_HASH"
The YAML above is a proposal template, not a live capture.
Replace both placeholders from the server in front of you.
Leave either placeholder and the gate must fail closed.
A matching digest proves identity for that start only.
It does not prove the free server will exist next week.
Treat permanence as unknown unless current docs say otherwise.
Mistake 2: An open tree looked clean
You asked the model to fix the retry bug and tidy comments.
The diff touched the helper, the test, a config, and the lockfile.
Clear scope was lost inside a clean-looking patch.
Comments were never the failing assertion in the test.
Unrelated edits were the defect that burned the review.
A tidy style pass is not a ticket boundary.
Write the path fence before the model is allowed to write.
Put only the ticket files into the allowlist for this run.
Deny lockfiles, build output, and dotenv files by default.
{
"ticket": "T-184",
"allow": ["src/retry.ts", "test/retry.test.ts"],
"deny_globs": ["**/*.lock", "**/dist/**", "**/.env*"]
}
That JSON is proposal data for one imaginary ticket.
Swap the paths for the files your test actually imports.
Do not add cleanup paths because the model suggested them.
Mistake 3: Prose replaced the stat file
The closing note said the tests passed on the free server.
You never saved the exit code next to the ticket.
You never saved git diff stat output either.
A confident paragraph can describe a run that did not happen.
A stat file cannot invent paths the tree does not have.
Accept the run only when names match the allowlist.
Keep the chat for context, not for acceptance.
Store the command output beside the ticket id.
Read that file before you read the model's last line.
How the three mistakes stacked
The floating base made the second run unequal to the first.
The open tree made that unequal run also unreviewable.
The missing stat made both problems look like a model miss.
You spent the day swapping prompts instead of files.
The base image and the path fence were the real variables.
Prompt edits cannot pin a digest you never stored.
Fence check you can copy
The script below is unexecuted proposal code for a local check.
It does not call a network and it does not start a server.
Run it only inside a repo you are allowed to modify.
// proposal: unexecuted fence check. Not a recorded live run.
import { readFileSync, writeFileSync } from "node:fs";
import { execFileSync } from "node:child_process";
const fence = JSON.parse(readFileSync("fence.json", "utf8"));
const pin = readFileSync("pin.yml", "utf8");
if (!pin.includes("sha256:")) {
throw new Error("pin.yml has no digest line");
}
if (pin.includes("REPLACE_WITH")) {
throw new Error("pin.yml still has placeholders");
}
const names = execFileSync("git", ["diff", "--name-only"], {
encoding: "utf8",
})
.trim()
.split("\n")
.filter(Boolean);
const outside = names.filter((name) => !fence.allow.includes(name));
if (outside.length > 0) {
throw new Error("diff left the allowlist");
}
const stat = execFileSync("git", ["diff", "--stat", "--", ...fence.allow], {
encoding: "utf8",
});
const result = {
ticket: fence.ticket,
names,
stat,
accepted: true,
};
writeFileSync("gate-result.json", JSON.stringify(result, null, 2));
The script reads pin.yml and fence.json from the working directory.
It throws if the pin still contains placeholder text.
It throws if any changed path sits outside the allowlist.
It writes gate-result.json with the names and the stat text.
A thrown error means you reset the tree before another prompt.
A written file means the write set matched the fence.
The proposal script checks exact allowlist names only.
Deny globs in the JSON stay a human checklist for now.
Add real glob matching before you trust that deny list.
This check reads the worktree diff, not a pushed commit.
Commit the fenced paths only after gate-result.json exists.
A pushed branch can still hide an earlier extra file.
Tests stay outside this gate for a reason you can audit.
You still run the fenced test command yourself and record it.
You still append that exit code to the result file.
Eight steps for the first hour
Do these steps in order on a server you may use.
Stop at the first missing file instead of prompting anyway.
Do not skip ahead because the closing note sounds complete.
- Start the free server and copy its image digest into pin.yml.
- Hash the lockfile you intend to keep and store that hash.
- Write fence.json with ticket paths only, plus deny globs.
- Note the server hostname beside the digest in the same pin file.
- Prompt with the fence text, not with a repo-wide cleanup ask.
- Apply the patch on the server, then run the fence check.
- Re-run only the fenced tests and append their exit codes.
- Accept only when names, digest, and exits all match the files.
How to read the signals
Use this table before you trust a closing note.
Each row is a rule, not a measured score.
None of these rows is a benchmark against another tool.
| Signal in hand | What it proves | Next action |
|---|---|---|
| Closing note says fixed | A sentence was produced | Do not accept |
| Names match allowlist | Write set matches ticket | Run fenced tests |
| A path sits outside | Scope leaked | Reset that tree |
| Live digest matches pin | Base identity matches | Keep the result file |
| Digest missing | Replay is blocked | Do not prompt yet |
| Exit code file exists | A command finished | Attach it to ticket |
| Lock hash changed | Dependencies moved | Open a new ticket |
Read the first column as input, not as proof.
Read the middle column as the limit of that input.
Read the last column as the only allowed next move.
Commands that build the result file
These commands are a proposal, not a log from a live box.
Run them on a server you are allowed to use.
Point them at the ticket directory, not at your home folder.
git rev-parse HEAD > ticket/head.txt
git diff --name-only > ticket/names.txt
git diff --stat > ticket/stat.txt
Use sha256sum only if that server image includes it.
Otherwise call the checksum tool already installed there.
Append the hash to pin.yml before you prompt.
Compare names.txt with fence.json before you open the chat.
If the lists differ, the summary is already late.
Fix the tree, then ask for a narrower note.
Limits you should say out loud
A pinned digest does not promise capacity on the free server.
Free model access does not name a quota in this article.
Hardware, duration, and uptime are unknown here on purpose.
A green fenced test does not prove CI uses your digest.
A path fence does not prove the allowed edit is correct.
You still need a test that fails for the original bug.
Hosted models can retain prompt text under their own policy.
Do not paste secrets, tokens, or private customer data.
A free server is still a remote machine you do not own.
Treat that shell as shared with the host operator.
Read the current docs before you plan a long job.
This draft does not claim the free option is permanent.
Who should not start here
Skip this loop for a local typo you can edit by hand.
Skip it when the server will not show an image digest.
Skip it when you cannot name an allowlist you will defend.
Skip it when policy forbids sending repository text to a host.
Skip it when the ticket needs production credentials in the shell.
Those cases need a different control, not a longer prompt.
After the letter
Tomorrow, pin the digest before you type the first prompt.
Write the fence before you ask for a fix.
Save the stat file even when the note sounds certain.
A free model and a free server can host that hour.
The three files still decide whether the hour counts.
That is the only next step this letter asks for.
Top comments (0)