You open the incident channel on Monday and find three agent retries stacked against one failed checkout ticket. The original prompt is gone, replaced by a shorter note that a teammate typed just before lunch. Nobody can say which tool schema the first call used, or whether the second call changed the system text. Shared model access still shows the spend, yet the wiki names no owner for that sequence of calls.
That missing record is the defect you can close without arguing about which model sounds smarter this week. A retry that overwrites the failed prompt destroys the only input evidence the ticket still had left. You need a wiki handoff that freezes the prompt, names the roles, and blocks the next call until that page exists.
What the freeze records
A prompt freeze is a small file you write before anyone is allowed to send the same job again. It stores a hash of the prompt, a hash of the tool schema, the ticket id, and the name of the retry owner. It does not claim the model was correct, and it does not replace a review of the patch the agent proposed. You are keeping the input that produced a bad or unclear result, so the next person debugs that input instead of a rewritten memory.
Teams skip the file because a second call feels faster than a short write-up in the wiki. On shared free model access, that habit spends a common pool and leaves the next on-call engineer with nothing to diff. You can keep the write-up to one wiki page plus a JSON file that a local script is able to check.
Roles and the handoff rule
You name four roles at the top of the page, even when one person holds two of them on a small team. The prompt owner keeps the frozen text and may change it only by creating a new freeze id. The retry owner is the only person who may send the job again after the freeze file exists. The schema owner confirms that the tool definitions match the hashed file, including argument names and required fields.
The scribe pastes the numbered run into the ticket and links the freeze file so the handoff is visible. These roles fail when they stay implicit and everyone assumes someone else still has the original text. The retry owner must not edit the prompt in the same step used to resend the job. If the prompt owner wants a new instruction, they mint the next freeze id and the retry owner waits for the check to pass.
The numbered run you paste into the wiki
You keep the run short enough to paste under the ticket, then you link longer notes beneath that block. You number every step and you do not skip a line when a field is still unknown. An unknown field is a reason to stop the resend, not a reason to invent a plausible value.
- Copy the exact prompt that failed into
prompt.txt, including system text and user text, without tidying a single sentence. - Copy the tool schema the job actually loaded into
tools.json, and ask the schema owner to confirm that filename in the ticket. - Run the freeze script so it writes
freeze.jsonwith both hashes, the ticket id, and a UTC timestamp from the machine clock. - Paste the freeze id, both hashes, and the four role names into the wiki page before anyone opens a model client again.
- Let the retry owner resend only when the check command prints MATCH against those same two files.
- If the prompt must change, stop, assign a new freeze id, and repeat from the first step instead of editing the old record.
You treat a missing file as a failed handoff rather than as a detail you can fill from memory. You do not reconstruct the prompt from chat recall and then call that reconstructed text a valid freeze. If the log viewer already dropped the text, you mark the page prompt_unavailable and you refuse the retry. You wait until someone recovers the text from client history or from version control before a new freeze exists.
Hash the prompt and the schema differently
The script below is an example you can copy into a scratch repository and run on your own machine. This example is unexecuted in this article, so you should run it on sample files before you adopt it. It does not call a model, and it is not a security boundary or an approval system for production changes.
You hash the prompt as raw bytes, because a changed space can change the model input even when the words look the same. You hash the schema from canonical JSON with sorted keys, so a cosmetic reorder does not look like a tool change. You should read every branch before you trust it, and you should keep this helper beside the wiki page the scribe linked.
#!/usr/bin/env python3
"""Example prompt freeze. Not a security boundary."""
import argparse
import hashlib
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
def prompt_digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def schema_digest(path: Path) -> str:
parsed = json.loads(path.read_text(encoding="utf-8"))
canonical = json.dumps(
parsed, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
def write_freeze(prompt: Path, schema: Path, ticket: str, owner: str, out: Path) -> None:
record = {
"ticket": ticket,
"retry_owner": owner,
"prompt_sha256": prompt_digest(prompt),
"schema_sha256": schema_digest(schema),
"frozen_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"retry_allowed": False,
}
out.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8")
print(f"WROTE {out}")
def check_freeze(prompt: Path, schema: Path, freeze: Path) -> int:
record = json.loads(freeze.read_text(encoding="utf-8"))
ok_prompt = record.get("prompt_sha256") == prompt_digest(prompt)
ok_schema = record.get("schema_sha256") == schema_digest(schema)
allowed = record.get("retry_allowed") is True
if ok_prompt and ok_schema and allowed:
print("MATCH")
return 0
print("MISMATCH")
print(f"prompt_ok={ok_prompt} schema_ok={ok_schema} retry_allowed={allowed}")
return 1
def main() -> int:
parser = argparse.ArgumentParser(description="Freeze or check an agent prompt.")
parser.add_argument("--prompt", required=True, type=Path)
parser.add_argument("--schema", required=True, type=Path)
parser.add_argument("--ticket", default="unset")
parser.add_argument("--retry-owner", default="unset")
parser.add_argument("--out", type=Path)
parser.add_argument("--check", type=Path)
parser.add_argument("--allow-retry", action="store_true")
args = parser.parse_args()
if args.check:
return check_freeze(args.prompt, args.schema, args.check)
if args.out is None:
print("need --out or --check", file=sys.stderr)
return 2
write_freeze(args.prompt, args.schema, args.ticket, args.retry_owner, args.out)
if args.allow_retry:
record = json.loads(args.out.read_text(encoding="utf-8"))
record["retry_allowed"] = True
args.out.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8")
print("RETRY_FLAG_SET")
return 0
if __name__ == "__main__":
raise SystemExit(main())
You run the write step first, and you set the retry flag only after the wiki paste is actually in the ticket. The flag is a team convention inside the JSON file, not an access-control gate on the model client. You still depend on the retry owner to honor the flag, which is why the role is named on the page.
python freeze_prompt.py --prompt prompt.txt --schema tools.json \
--ticket CHKT-1842 --retry-owner "sam" --out freeze.json
python freeze_prompt.py --prompt prompt.txt --schema tools.json --check freeze.json
python freeze_prompt.py --prompt prompt.txt --schema tools.json \
--ticket CHKT-1842 --retry-owner "sam" --out freeze.json --allow-retry
python freeze_prompt.py --prompt prompt.txt --schema tools.json --check freeze.json
The check command should print MISMATCH until you create a fresh freeze that includes the allow-retry flag. That order is deliberate, because a copied command should not look successful before the handoff is done. If both hashes match and the flag is true, the script prints MATCH and the retry owner may send the single allowed call.
Sample files, not a captured job
The JSON shape below is a sample layout, not a capture from a live job, so you replace every field when you run the script. You can start from the tiny tool file, reorder its keys, and confirm the schema hash stays stable. You should not point this helper at YAML or at JSON with comments, because the parser will reject the file and the handoff will stop.
System: You may call lookup_order only. Do not invent an order status.
User: The checkout ticket says order 1842 failed after payment. Report the tool result only.
{
"name": "lookup_order",
"parameters": {
"type": "object",
"required": ["order_id"],
"properties": {
"order_id": {"type": "string"}
}
}
}
{
"ticket": "CHKT-1842",
"retry_owner": "sam",
"prompt_sha256": "<raw sha256 of prompt.txt>",
"schema_sha256": "<canonical sha256 of tools.json>",
"frozen_at": "2026-10-13T15:04:00Z",
"retry_allowed": false
}
Decision table for the retry owner
You do not resend the job just because a freeze file happens to sit in the working directory. You pick one row from the table and you record that row name in the ticket before the call. The table is a handoff aid, not a scoring model, and it never tells you to retry until a log turns green.
| What you see | Freeze status | Next action |
|---|---|---|
| Same prompt, transport error, schema unchanged | Hashes match and retry flag is true | Retry owner sends once, then stops for a human read |
| Confident but wrong tool call | Hashes match | Prompt owner writes a new freeze; do not resend the same text |
| Schema file differs from what the job loaded | Schema hash mismatch | Schema owner repairs the file; nobody retries |
| Prompt text cannot be recovered | No freeze | Scribe marks prompt_unavailable and stops |
| Ticket holds secrets or unredacted customer text | Any | Stop; redaction is a separate review, not a retry |
A later success does not prove that the first prompt was sound, especially when the text changed between calls. You use the freeze so a reviewer can diff the input that failed against the input that finally ran. If those hashes differ, you are looking at a new experiment, and you should label it as one.
Use free access without inventing a budget
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode, in the facts supplied for this draft, provides free model access and a free server option you may use for this handoff. The retry owner can send the single allowed resend through that free model access, if the option is still offered when you run. You can keep the freeze script on the free server option when that host exists, rather than buying hardware for a hash. You do not write a token quota, a hardware size, a duration, or a permanence claim into the wiki unless current docs state them.
A stale number in the runbook will train the team to trust a budget that the product page no longer shows. You confirm the live terms before you link them, and you leave the quota line blank when the docs do not publish one. The useful constraint is the shared pool, because an undocumented retry spends attention and access that other tickets still need. If the free server option is absent on the day you need it, you still run the script anywhere Python 3 is installed.
You still refuse the resend until the wiki page and the freeze file both exist, even when the host is only a laptop. The product name is optional to the method, and the method should still work if you remove every mention of it. If you want one next step, add this page to the team wiki and point the retry owner at the check command.
Review the handoff in ten minutes
- Open the freeze file and confirm the ticket id matches the page you pasted, including the prefix and the number.
- Re-run the check command and keep the MATCH line in the ticket so a later reader sees the files were stable.
- Ask the schema owner whether any tool argument changed after the hash, and stop if the answer is uncertain.
- Write one sentence on what the new output did differently, and link it under the freeze id rather than over the old note.
You stop after one allowed resend unless a new freeze id exists, even when the output looks almost right. A second silent retry is how the Monday channel filled up with three calls and zero recoverable prompts. The page is done when a teammate who was absent can see who may call next and which files they must not edit.
Who should not use this page
This approach fails when the prompt is built in memory and never written to a file you can hash. It also fails when two jobs share one prompt file and you overwrite that file between the freeze and the check. You should not use the hash as a compliance archive, a secret scanner, or proof that an agent patch is safe to merge. Teams that already store immutable prompt versions in their orchestrator can skip the extra ritual unless retries drop that text.
You should skip any hosted model path when the ticket holds secrets, payment data, or text your policy forbids you to send. The freeze script can still run on redacted local fixtures, but the resend must wait for a route your policy allows. A matching hash is not permission to paste raw customer logs into a model, whether that model is free or paid. You also should not expect this page to catch a model that agrees too quickly with a flawed premise.
The freeze only keeps that premise stable long enough for a person to read the words before the next call. Judgment stays with the prompt owner and with the reviewer who inspects the tool call, not with the hash itself. If your team cannot spare a named retry owner, do not pretend the script will enforce the handoff for you.
Top comments (0)