DEV Community

Quinn Sun
Quinn Sun

Posted on

The Senior Froze the Runtime Before the Agent Proposed a Patch

The junior had already highlighted src/retry.mjs. The ticket was small: stop a webhook worker from firing twice when the upstream returned 409. The senior did not open the helper. The senior opened a blank buffer named assumptions.yml, typed three keys, and stopped.

The pairing session did not start with a prompt. It started with a list of facts a coding agent was forbidden to invent.

The scene

The worker was a Node service. CI used the Node major pinned in the Dockerfile. Laptops did not. The junior wanted to paste the helper, the test, and a sentence that said “be careful with timeouts” into a free coding model, then keep the first patch that made the unit file green.

The senior refused that order. A green test against an invented runtime is a false green. The agent would not join the on-call rotation.

Questions the senior asked out loud

The questions went into the pairing notes, not into the model box. Prompts leak. Notes stay.

  1. Which Node major is pinned in the Dockerfile, and which major is on this laptop right now.
  2. Whether src/retry.mjs owns the sleep, or the caller injects a clock.
  3. Whether AbortSignal is already a given on the pinned runtime, or a polyfill the codebase has never shipped.
  4. The public signature of retryWebhook, and every import outside tests.
  5. If package-lock.json and the running container disagree, which file is the source of truth for this patch.

They answered from cat and git grep. They did not answer from memory. Two of the five answers changed after the Dockerfile was opened.

Dead end 1: let the agent look around

The first pass skipped the ledger. They pointed a coding agent at a throwaway checkout and asked it to inspect the tree, then patch the helper. The run was easy to throw away. The patch was not usable.

The agent assumed Express middleware shapes in a comment. The service was not an Express app. It also rewrote setTimeout into AbortSignal.timeout because “modern Node has it.” The Dockerfile still pinned a Node that did not.

They discarded the diff. The cost of the mistake was a revert, not a production incident. That was luck, not a method.

Dead end 2: dump package.json and hope

The second pass stuffed package.json into the prompt and added “do not upgrade dependencies.” The model obeyed the letter. It still invented a runtime API that no dependency had declared.

A manifest is not a runtime. The senior said that once, then wrote it into the ledger as a rule the agent could not extend.

Dead end 3: “read the code and be careful”

The third prompt was a pep talk. Pep talks are not invariants. The agent produced a tidy exponential backoff that changed retryWebhook(url, body, n) into retryWebhook(opts). Every caller would have moved. The ticket was a 409. The ticket was not a signature migration.

That patch went into /dev/null with the other two.

The decision they kept

After the third revert they stopped prompting. They finished the YAML. The agent was allowed to run only after assumptions.yml sat on the pairing branch. A patch was allowed only if a local checker found no invented runtime fact.

Pairing produces the facts. The agent produces the diff. The checker refuses the mix-up. That split is the whole method.

Artifact: the assumption ledger

The file is small on purpose. It is not a second README. It is the set of claims the agent may treat as true, plus the gaps it must not fill.

# assumptions.yml
# Pairing-owned. The agent may read this file. The agent may not extend it.
runtime:
  language: node
  major: 20
  source_of_truth: Dockerfile
ticket:
  id: WH-409
  files_in_scope:
    - src/retry.mjs
    - src/retry.test.mjs
api:
  function: retryWebhook
  signature: "(url: string, body: unknown, attempts: number) => Promise<Response>"
  sleep_owner: helper
  clock_injectable: false
forbidden_inventions:
  - AbortSignal.timeout
  - express
  - renamed retryWebhook
  - new dependencies
unknowns:
  - production timeout budget
  - whether HTTP 409 is retryable or a hard stop
policy:
  on_unknown: stop
  on_conflict_with_this_file: reject_patch
Enter fullscreen mode Exit fullscreen mode

The unknowns list did more work than the allowed files. Pairing named the gaps. The agent was not invited to vote on product behavior.

Artifact: a checker the session can run before git commit

The script does not prove the patch is correct. It only proves the patch did not smuggle a fact pairing never froze. Treat it as a pairing helper, not a security boundary.

// check-assumptions.mjs
import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";

const ledger = readFileSync("assumptions.yml", "utf8");
const inScope = [...ledger.matchAll(/^\s+-\s+(\S+\.(?:mjs|js))/gm)].map((m) => m[1]);

const nameOnly = spawnSync("git", ["diff", "--cached", "--name-only"], {
  encoding: "utf8",
});
if (nameOnly.status !== 0) {
  console.error("git diff --cached failed");
  process.exit(2);
}

const changed = nameOnly.stdout.trim().split("\n").filter(Boolean);
const extra = changed.filter((f) => f !== "assumptions.yml" && !inScope.includes(f));
if (extra.length) {
  console.error("assumption checker: files outside the ledger\n" + extra.join("\n"));
  process.exit(1);
}

const diff = spawnSync("git", ["diff", "--cached", "--", ...inScope], {
  encoding: "utf8",
});
const patch = diff.stdout;

const bans = [
  /AbortSignal\.timeout/,
  /express/i,
  /retryWebhook\s*\(\s*\{/,
  /from ['"]node:timers\/promises['"]/,
];

const hits = bans.filter((re) => re.test(patch));
if (hits.length) {
  console.error("assumption checker: patch invents a frozen-away fact");
  for (const re of hits) console.error(" - matched", String(re));
  process.exit(1);
}

console.log("assumption checker: ok");
Enter fullscreen mode Exit fullscreen mode

The pairing session ran the same two commands every time the agent returned a diff:

git add src/retry.mjs src/retry.test.mjs assumptions.yml
node check-assumptions.mjs
Enter fullscreen mode Exit fullscreen mode

A failed checker meant the YAML changed, or the patch changed. It did not mean another sentence of “please be careful” went into the prompt. Careful is not a file.

A constrained prompt that is allowed to say STOP

The prompt they kept was shorter than the pep talk they threw out. It is an example, not a benchmarked template.

Read assumptions.yml first. Treat it as the only true runtime.
Edit only files_in_scope. Keep the function signature byte-for-byte.
If the task requires a fact listed under unknowns, reply STOP and name the unknown.
Do not add facts to the YAML. Return a unified diff or STOP.
Enter fullscreen mode Exit fullscreen mode

The useful output on the third retry was not a diff. It was STOP: whether HTTP 409 is retryable or a hard stop. That line sent the decision back to the humans, which is where it belonged.

Where a free model and a free server actually fit

The team needed a loop that could re-run the same constrained prompt after each ledger edit. They did not need the model to discover the runtime. Discovery had already failed three times before lunch.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

MonkeyCode’s free model access and free server option were used as that disposable loop: ledger in, two in-scope files in, patch or STOP out. When the checker failed, they changed one YAML line or one assertion and ran the same prompt again. The product did not become the source of facts. The pairing session stayed the source of facts. Readers who already freeze runtime claims in notes can use that free-model and free-server path as a rerun loop, nothing more.

Decision table from the session

Situation Pairing does Agent may
Dockerfile Node major differs from the laptop Freeze the Dockerfile major in YAML Patch against that major only
Function signature is fuzzy Write the signature from call sites Keep it byte-for-byte
Product rule is unknown (409 retry?) Put it under unknowns Reply STOP. Do not pick a policy
Test wants a fake clock Decide clock ownership first Change the helper only after that
Patch needs a third file Humans extend the ledger, or reject Never self-expand scope

Limitations

Regular expressions do not understand intent. The checker will miss a clever rename of retryWebhook. It will also trip on a comment that mentions Express in order to say Express is out of scope. Someone still has to read the diff.

The ledger goes stale the moment an image bump lands without a YAML bump. Treat a CI Node-version failure as a ledger bug, not as an agent bug.

The method is a poor fit for a spike. If the work is “what could replace this helper,” the unknowns are the work. Freeze nothing, or freeze only the files that must not be deleted, and explore without a patch checker.

Who should not use this

  • Anyone hoping a model will infer production timeouts from comments.
  • Greenfield branches where the public signature is still moving.
  • Repositories that do not review git diff --cached as the change surface.
  • Sessions that would paste credentials, customer payloads, or internal URLs into a free-server prompt. The ledger holds facts. It does not hold secrets.

The decision that stayed on the branch

They merged a twelve-line change to src/retry.mjs. The 409 path stopped retrying. The function name did not move. Node 20 stayed Node 20. assumptions.yml was the longest file in the pull request, and that was the point.

Three agent plans were deleted. One YAML file was not. The cheap reruns only paid for themselves because the runtime had already been frozen by two people who could open the Dockerfile.

Top comments (0)