The first five commands after a page should be sealed, read-only, and boring, because creativity at 3 a.m. is how incidents get worse.
I want every alert to land on a contract, not a chat transcript that an agent can rewrite.
Have you noticed how quickly an assistant jumps from checking latency to restarting a deployment under pressure?
That jump is the real bug, and a linter can catch it before the next on-call rotation starts.
The failure is not the model.
It is the missing seal.
Agentic tools are getting better at sounding like they belong in the incident channel.
They also assume missing facts, which is the same failure people keep reporting when they wire agents to cloud APIs.
Why should a paging bot invent when your freeze calendar already forbids writes?
Why should the first command after a page ever be a mutation?
I treat the opening of a runbook as a sealed list, not as a prompt the model can keep editing.
The model may draft the narrative, the diagrams, and the paragraph that explains why the alert exists.
It does not get to author the first commands, the escalation timer, or the freeze switch.
Those three fields are the contract, and they belong in YAML that a test can fail in CI.
What the contract actually contains I keep four blocks, and I refuse to ship a runbook that is missing any of them.
Does your current wiki page even list the first command as a string you can grep?
If not, an assistant will happily fill the gap with something that sounds like expertise.
Alert match — name, severity, and the condition that turns a ticket into a page.
First commands — five or fewer read-only checks, each with a hard timeout.
Escalation — a clock, a human role, and a stop condition that is not vibes.
Freeze / unfreeze — a write lock on the mutating allowlist, not a footnote.
The order matters more than the prose.
Humans under a page will run whatever sits at the top, and models will imitate that habit.
If the top is sealed and dull, the rest of the incident has a chance to stay honest.
A YAML runbook you can actually lint The following example is a proposal you can save as and validate locally.
It is not a production dump from a company, and I am not claiming it pages a real cluster.
Look at .
That boolean is the whole point of the file.
If a generator rewrites those steps, the linter should fail the pull request, not the incident.
Would you merge a runbook that lets a chatbot swap for because the prompt said be helpful?
A freeze calendar the linter can fail closed on Pair the runbook with a calendar file.
Empty rumors in Slack are not a freeze source of truth.
Freeze is a write lock on the allowlist I do not want freeze to mean please be careful tonight.
I want freeze to mean the mutating allowlist is empty until two humans spend a TTL.
Unfreeze is an explicit event with two roles and a short clock, because a permanent unfreeze is just another wiki lie that nobody re-reads.
State First commands Mutating remediations Page?
Who can change the lock freeze sealed, read-only blocked yes two roles, TTL unfrozen still sealed, read-only allowlist only yes lock returns after TTL no calendar fail closed fail closed ticket only owner must add a calendar Would you let an agent flip that table because the system prompt said be helpful?
I would not, and neither should your pager.
During freeze, the only honest remediation text is collect evidence, then wait for a named unfreeze.
After a valid unfreeze, you may run a separate allowlisted command, never a rewritten first command.
The seal does not thaw when the lock opens.
That is the rule I want sitting next to every chatbot integration.
A small Python linter for the contract Save this as .
Treat it as a local check you can run in CI.
I am not publishing pass or fail metrics from a fleet I do not have.
A matching stub keeps reproducible without pretending you already have a freeze service.
Run the happy path, then break it on purpose: Copy the YAML, change to , and run the linter again.
If it does not exit 1, you do not have a seal.
You have a suggestion that will fire during a real page.
Where a free model still belongs I still want help writing the why this alert exists section, because humans abandon runbooks that read like legal documents.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode is an open-source project with free model access and a free server option, which is enough for the drafting half of this workflow if you do not want that loop on a production laptop.
The prompt I actually send is intentionally rude to the model, because polite prompts invite invented flags.
If the model cannot obey that, I paste the prose back by hand and leave the commands untouched.
Have you tried asking an agent to stop being helpful?
It is a surprisingly good filter for on-call text.
A tiny workflow that stays honest looks like this: A human writes the sealed commands, the escalation clock, and the freeze block first.
A model drafts owner notes and the when-to-page story, never the command strings. runs in CI on every pull request that touches .
Unfreeze remains a separate two-person action in the incident tool, with a TTL.
That is the whole loop.
No dashboard screenshots, no invented latency wins, and no claim that the bot closed the incident.
Who should not use this Skip this contract if you do not own the cluster, because a sealed list is still a credential problem.
Skip it if your alerts are not mapped to a single service owner, because escalation to a role that does not exist is theater.
Skip it if you need the bot to execute remediations unattended, since this design refuses that job on purpose.
Also skip it if your freeze calendar is a rumor in chat.
The linter fail-closes without a calendar path, and that will annoy you until you write the file.
Good.
Annoyance before the page is cheaper than a restart during a payment cutover.
Limitations I will not paper over This linter does not prove a command is safe.
It only proves the string looks read-only and the freeze block is present.
A to the wrong internal URL can still be harmful, and a on a huge pod can still stall a laptop.
Timeouts are a courtesy, not a sandbox, and they will not save you from a bad kubeconfig.
I also cannot claim a specific model name, quota, or hardware profile for the drafting step, because those numbers go stale and I will not invent them.
The value is the seal, not the vendor sticker.
If you strip every product name out of this article, you should still have a YAML file, a calendar, and a Python exit code.
One more uncomfortable question sits under all of this.
If your runbook cannot fail a unit test, why would you let an agent touch it during a page?