Picture this: a 2:51 a.m. page hits your phone, and your only resource is a runbook that was written in a Google Doc three years ago. The database is cursing, the call is cryptic, and the escalation policy reads like a flowchart from a regulatory filing. I built a different kind of runbook—one that executes on a free server, calls MonkeyCode's free models for context, and replies with the first command before your coffee is even cold.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
The idea is simple: route every alert through a tiny serverless function that holds your runbook's decision table, parses the alert text, asks an LLM what to do, and returns a concrete starting point. MonkeyCode provides a free server to host that function and a ten-million-token allowance to power the LLM calls, so the entire experiment fits inside a free tier without leaking dollars into your incident budget.
Why a runbook should talk back
Most runbooks are passive documents; they sit in a wiki until someone remembers to open them during an incident. A runbook with a pulse, by contrast, accepts an alert payload, applies a freeze rule, and returns a command that a tired human can run without second-guessing the syntax. The goal is not to replace judgment—it's to compress the gap between a raw page and a useful action, especially when the alert text is ambiguous or the service topology has shifted since the last incident review.
The trick is treating the LLM as a circumstantial interpreter, not an oracle. You decide the paths, and the model helps classify the alert into one of those paths. That separation keeps behavior auditable while still letting natural language bridge the gap between what your monitoring says and what your engineers actually need to type.
The architecture in one diagram
A webhook from PagerDuty or Alertmanager lands on a small HTTP endpoint. That endpoint looks up the team's freeze window, extracts a few fields from the alert, and sends a compact prompt to MonkeyCode's model. The model returns a recommendation, and the function either prints it on the channel or sends it back to the ticketing system. Here's the shape I used:
# alert_bridge.py
import os, datetime, requests
def handler(alert):
if is_frozen(alert):
return {"action": "acknowledge", "message": "Change freeze in effect; no runbook action."}
runbook = load_runbook_table()
endpoint = os.getenv("MONKEYCODE_ENDPOINT")
key = os.getenv("MONKEYCODE_KEY")
prompt = build_prompt(alert, runbook)
r = requests.post(endpoint, headers={"Authorization": f"Bearer {key}"}, json={"messages": prompt})
return r.json()
def is_frozen(alert):
now = datetime.datetime.now(datetime.timezone.utc)
return now.month == 12 and now.hour < 8 # example freeze rule
def build_prompt(alert, runbook):
return [
{"role": "system", "content": "You turn alerts into runbook steps. Answer with one command and one escalation note."},
{"role": "user", "content": f"Alert: {alert['text']}\nRunbook: {runbook}"}
]
The function is deliberately short; the decision table does the heavy lifting, and the model only needs to map the alert to a row. I stored the table in a separate YAML file so non-engineers can review it during planning sessions.
A decision table for first response
| Severity | Signal | First action | Escalate if |
|---|---|---|---|
| P1 | API error rate > 5% | Restart the gateway service | No recovery in 5 minutes |
| P2 | Disk usage > 85% | List largest files with du -h
|
Growth continues after 30 minutes |
| P3 | Latency p99 > 300 ms | Check recent deploy and rollback one revision | p99 > 800 ms for 10 minutes |
| P4 | Metric noise | Add a comment, do not notify humans | Threshold breached twice in an hour |
That table is the contract between monitoring and action. The LLM never invents new steps; it just selects the right row and formats the command with the current hostname and cluster ID from the alert payload.
Adding a freeze and unfreeze rule
Freezes are the Achilles' heel of automation—the same model that suggests a restart can easily push a production change during a release blackout. My freeze rule lives in the same handler and checks two dimensions: calendar windows and a cheap flag stored in a local file or a volatile key-value store. When a freeze is active, the bridge suppresses all actionable output and only sends an acknowledgment, which prevents the bot from acting on a system that is already in a fragile state.
# freeze.sh — toggle by writing a timestamp to a volume
curl -X POST http://localhost:8080/freeze -d '{"enabled": true, "until": "2026-09-05T00:00:00Z"}'
The refreeze path is just as simple: the handler checks until on every invocation and auto-clears itself when the timestamp passes. I added an explicit unfreeze endpoint for emergencies, because relying on time math alone is a quiet way to wake up in the middle of a freeze and discover the rule never turned off.
Deploying to a free server without losing your weekend
MonkeyCode's free server tier is meant for experiments, not production traffic, and that's exactly what this runbook bridge deserves. I deployed the function as a lightweight container and pointed a free HTTPS domain at it, then added webhook URLs from two monitoring platforms. The whole process, including writing the Dockerfile and configuring environment variables, took about twenty minutes.
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install flask requests pyyaml
CMD ["python", "app.py"]
Expose the port, bind the webhook endpoints, and test with a fake alert using curl:
curl -X POST http://localhost:8080/alert \
-H "Content-Type: application/json" \
-d '{"text": "disk usage 87% on db-01", "severity": "P2"}'
A sane response would be: du -h /var/lib/mysql | sort -hr | head -20 plus a note to check slow queries. If the model returns something off-topic, you tighten the system prompt or simplify the decision table.
Where this approach breaks
This free-tier setup is not for organizations with strict audit requirements or for alerts that demand a human's hands-on debugging before a command runs. The token allowance resets periodically, so high-volume incident streams could exhaust it mid-crisis, and the server has no SLA, so do not place it on the critical path for life-or-death infrastructure. Also, the model can misread a terse alert even with a good table; that is why the output is a suggestion, not an action that executes automatically.
I also would not use this for regulatory compliance incidents where every step needs a signed-off owner. Keep the AI as a front-line interpreter, not a decision authority, and always log the raw prompt and response for post-incident review.
Try it with your own alerts
The pain of a static runbook is universal, and the fix is now cheap enough to test in a spare afternoon. Pull the MonkeyCode project, grab your free token allowance, and point a webhook at this tiny bridge—then let the first page you receive be a reminder that on-call exhaustion is optional.
Top comments (0)