How to Write a Runbook for an Unattended AI Agent
An unattended agent is not a script you run; it is a service you own at 3 a.m. A runbook is the operator manual for that service: what must be true before the loop starts, what to check when the agent goes quiet, how to tell one failure class from another, and who gets woken up. Write it before the first scheduled run, not after the first silent week.
Preconditions: what must be true before the first scheduled run
Preconditions fail fast, before the agent spends money or writes anything. When one fails, the run exits with a distinct code and touches nothing. The point is to convert “the agent did something strange” into “one specific assumption was false.”
Keep a manifest in the repo next to the agent code, reviewed the same way as the code:
# runbook.yaml โ one per agent
agent: invoice-reconciler
owner: platform-oncall # an existing rotation, not one person's name
entrypoint: "python -m agents.reconciler --once"
schedule: "0 */2 * * *"
max_runtime_seconds: 900 # hard kill; the agent must not outlive its slot
budget:
per_run_usd: 0.75
per_day_usd: 12.00
state_store: "postgres://state-db/agent_state"
idempotency_key: "sha256(input_revision + prompt_version)"
kill_switch: "s3://ops-flags/agents/invoice-reconciler/disabled"
alerts:
page: agents-primary
log: "#agents-log"
The checklist behind that file:
- Credentials have an expiry date and a written rotation step. A tool token with no owner is an outage waiting for a calendar.
- Inputs have a schema and a freshness bound. Reading yesterday’s export twice is a state error, not a model error.
- The output destination tolerates being written twice, or the write is wrapped in an idempotency key.
- The kill switch can be flipped without SSH โ a flag in object storage or a config service, not a shell on the box.
run_idandprompt_versionare written to state on every run. Correlating a failure streak with a prompt deploy is guesswork otherwise.
If you are still designing the loop itself, the broader build order matters more than any single check โ see the complete automation framework for how the pieces fit together.
Health checks: liveness, readiness, and “did it actually do work”
Three layers, three signals, and they should not run inside the agent process. A checker that dies with the agent checks nothing.
Liveness. Each step writes a heartbeat timestamp to the state store. A separate scheduler compares the newest heartbeat against 2-3x the expected step interval.
Readiness. A cheap, read-only call per dependency: model health endpoint, SELECT 1 against the state store, one authenticated HEAD per external tool. A 401 on a tool credential is a config bug you want to hear about at 09:00 in Slack, not from a customer.
Productivity. Exit code 0 is not success. An agent that runs cleanly and writes zero rows has failed. Check the artifact, not the process: rows written in the last window, messages sent, files produced. This is the check teams skip and the one that catches silent no-ops.
#!/usr/bin/env bash
# check_agent.sh โ run every 5 minutes by a scheduler that is not the agent
set -euo pipefail
# liveness: heartbeat newer than 3x the 10-minute step interval
hb=$(cat /var/lib/agent/last_heartbeat)
age=$(( $(date +%s) - $(date -d "$hb" +%s) ))
[ "$age" -lt 1800 ] || { echo "STALE_HEARTBEAT ${age}s"; exit 2; }
# readiness: dependencies reachable
curl -fsS --max-time 5 "$MODEL_HEALTH_URL" >/dev/null || { echo "MODEL_UNREACHABLE"; exit 3; }
psql "$STATE_DSN" -tAc "select 1" >/dev/null || { echo "STATE_UNREACHABLE"; exit 4; }
# productivity: did the last window produce anything at all?
runs=$(psql "$STATE_DSN" -tAc \
"select count(*) from runs where started_at > now() - interval '3 hours'")
[ "$runs" -gt 0 ] || { echo "NO_RUNS_3H"; exit 5; }
echo OK
Exit codes are the interface. Keep the mapping in the same file as the check so it cannot drift: 2 and 5 page, 3 and 4 page once and then suppress for an hour.
Three failure classes, three different responses
Most runbooks collapse everything into “the agent broke.” That produces retries exactly where retries make things worse.
| Failure class | Signature | Blast radius | Retry? | First move |
|---|---|---|---|---|
| Model error | 429/5xx, timeout, schema-invalid JSON, refusal | one step | yes, bounded | retry with jitter, then fall back to a smaller model or dead-letter |
| Tool error | 401/403, 404 on a moved resource, 4xx validation | one run, sometimes a batch | only for 429/5xx | stop the run, discard the staged batch, fix the credential |
| State error | duplicate rows, cursor past unprocessed items, two workers on one partition | your data | never | freeze writes, page a human, reconcile against the source |
Model error. Split transient from semantic. Rate limits, 5xx and timeouts deserve three retries with jitter and a cap on total wait. Invalid JSON, a hallucinated tool name and a refusal do not get better with repetition โ send one repair prompt with the schema attached, then dead-letter the item. This is where versioned prompts earn their keep: context engineering is mostly about making the input to a retry different from the input that just failed.
Tool error. The real risk is a partial commit: rows written, then the downstream API rejects the batch. Force the order read โ stage โ call โ commit, so a failed tool call leaves only staged rows that can be dropped. A 4xx that is not a rate limit is a config bug; route it to Slack with the failing credential name and do not wake anyone.
State error. The only class that needs a human within minutes. The fix requires deciding which side is the truth โ your state store or the source system โ and that is a judgment call, not an algorithm. Design against it: a unique constraint on run_id, an idempotency key derived from the input revision, and a daily reconciler that compares source count to destination count and alarms on drift.
Who gets paged, and how the loop closes
Page only when all three are true: the loop is stopped, the work is time-bounded (someone is waiting on it), and no automatic recovery exists. Everything else goes to a channel. Model errors after retries and tool 4xx failures are log items. State errors, a tripped kill switch, an exhausted budget and a missed productivity check are page items โ and state errors ignore quiet hours.
Bind the agent to an existing rotation rather than a person. The page payload should be short enough to read on a phone:
AGENT invoice-reconciler
CLASS state
RUN run_2026_04_11_14 (last good: 12:00)
SYMPT duplicate rows in invoices_out, 42 staged, 0 committed
FIRST psql $STATE_DSN -c "select * from runs where run_id='run_2026_04_11_14'"
LOG https://logs.internal/agents/invoice-reconciler/run_2026_04_11_14
The loop closes on three habits, and none of them are optional:
- Every page produces a runbook entry โ symptom, check, fix, and one prevention line. The entry is what turns the next occurrence into a five-minute task instead of a rediscovery. The triage habits for automation that fails while you sleep cover the same ground from the on-call side.
- The dead-letter queue drains on a timer, with an alarm on its depth. A queue nobody drains is a slow-motion outage.
- A 15-minute weekly review of the run log: runs, runs that needed retries, dead-letter depth, budget burn. Rising retries mean the loop is not unattended, just unsupervised.
What to do next
- Write
runbook.yamlfor your oldest unattended agent today. Owner, entrypoint,max_runtime_seconds, budget ceiling, kill switch, idempotency key. It takes under an hour and it forces every vague assumption into a field. - Move the health checker out of the agent process. Three exit-code classes: liveness, readiness, productivity. Point them at a scheduler that survives the agent.
- Add
run_idandprompt_versionto your state table plus a unique constraint onrun_id. Run--dry-runonce against production input before the next scheduled window. - Write the page payload template once and paste it into your alerting tool. Then map the agent to an existing rotation so there is no “who owns this” on the night it fires.
- Schedule the weekly log review as a calendar event, and put the dead-letter drain on a timer with an alarm on depth.
If you want the scaffolding that sits around these practices โ prompt library, agent framework, deployment tooling and tutorials, pre-configured to work together โ the AI Developer Stack Bundle is a one-time $79. None of it replaces the runbook, but it removes a weekend of wiring before you get to write one.
Get AI Developer Stack Bundle
AI Developer Stack Bundle โ $79, one-time payment, instant download. See the full breakdown on the review page.
Related reading
- The Ultimate Guide to AI Automation 2026
- How I Built a 10-Product Digital Business
- Building an AI Content Factory
๐ Want to try before you buy?
Both samples are 100% free. Enter your email to unlock the download links and get our 3-day AI toolkit drip in your inbox.
No spam. Unsubscribe anytime. We respect your privacy.
Or browse the full catalogue in the Slashman Tools storefront.
Published by slashman413 โ writing practical, evergreen guides on money, productivity, developer tooling and the web. More about this site โ
Frequently Asked Questions
How often should the health checker run compared to the agent itself?
Run the checker on a separate scheduler at roughly the step interval of the agent, so a stalled step is caught within one or two cycles. It must not run inside the agent process, or it dies with the thing it is watching.
Should model errors ever page someone?
Only if they persist after bounded retries and the run cannot complete, which means the loop is effectively stopped. Transient 429s and timeouts belong in a log channel with a note about which item was dead-lettered.
What is the minimum state I need to store for a runbook to work?
A row per run with run_id, prompt_version, start and end timestamps, input revision and outcome, plus a heartbeat timestamp per step. That is enough to answer what ran, what it produced, and what changed before it broke.
๐ Recommended Tools
๐ Related Articles
- Every Product We Sell on Gumroad: The Complete CatalogEvery live Slashman Tools product on Gumroad in one place โ AI prompts, agent workflows, dashboards, courses โฆ
- What Makes a Feishu Template Worth Paying ForA breakdown of Feishu template parts: data model, views, permission scopes, automation flows โ and which ones โฆ
- Buying a Template vs Building Your Own: Variables That DecideA practical decision guide for buying a template versus building your own workspace: data model stability, โฆ
- n8n vs Custom Multi-Agent Workflows: When to Stop Dragging Nodes and Write Code (2026)n8n or a custom code multi-agent workflow? An honest 2026 comparison of versioning, testing, cost, LLM โฆ
๐ฌ Free Weekly AI Product Guides
New tools, templates and automation walkthroughs โ plus hands-on updates on the Slashman Tools catalogue. One email a week, zero fluff.
Free forever ยท No spam ยท Unsubscribe anytime ยท Sent instantly