How I built one-page runbooks for my AI companion's scheduled jobs: five receipts, outcome-keyed recovery, and a page that can never touch the task
How I built one-page runbooks for my AI companion's scheduled jobs My companion, Ankita, runs scheduled jobs while I'm away — a morning briefing, page watches, periodic digests. The failure mode I kept hitting wasn't a broken job. It was coming home to a job that failed with zero context: what doe

How I built one-page runbooks for my AI companion's scheduled jobs My companion, Ankita, runs scheduled jobs while I'm away — a morning briefing, page watches, periodic digests. The failure mode I kept hitting wasn't a broken job. It was coming home to a job that failed with zero context: what does this task even do, what did it last try, is it safe to run it again? Re-running a failed job blind is how you send the same Telegram message twice or re-trigger a half-finished action. So I built node chat.mjs runbook TASK_ID: a one-page Markdown runbook per task — what it does, its schedule and next run, its permissions, the last five outcomes, and recovery advice for the last status. The whole thing is 67 lines in src/automation/runbooks.mjs, and the interesting part is how defensive it is about trust. A runbook renders: task name, task ID, human-readable schedule (describeCron) with timezone, the next fire time via cronFire (or paused), the delivery channel, a permissions line (read/interact/login, browser mode, sites allowlist), then "What it does" — the task's prompt printed verbatim, indented four spaces — then "Recent outcomes", then "Recovery". The tricky part: outcomes live in two places. The scheduler keeps an in-memory runHistory, and the desktop keeps durable per-run receipt files ({task-id}-*.json) in a runs directory. retainedRuns() merges both: it filters history rows for valid timestamps, reads matching receipt files, dedupes everything on runId:at:status, sorts newest-first, and keeps the last five (RECENT_RUN_LIMIT = 5). The file-reading side is paranoid by design. A receipt is skipped — and counted — if its realpath escapes the runs directory (a symlink can't smuggle in another task's history), if it exceeds 2 MB (RECEIPT_MAX_BYTES, "only summary metadata is needed"), or if its metadata fails to parse. The page then says it out loud: N unreadable retained receipts were skipped. A deleted outcome is never reconstructed — "Existing tasks with only a last result show that one result honestly." Every line on the page passes through line(): whitespace collapsed, pipes escaped so tables can't be injected, truncated to JOB_SUMMARY_MAX_CHARS. And the finished report goes through redact() — the same secret scrubber I wrote about last week — because a task prompt or summary might contain a credential. The docs are upfront about the limit: the scrubber catches recognized patterns and labelled passwords, not every arbitrary low-entropy secret, so review before sharing. My favourite decision: the recovery section is a frozen map keyed to exactly six scheduler outcomes — error, timeout, interrupted, needs-input, budget, missed — with generic advice per status. The code comment says it plainly: "Guidance is keyed to scheduler outcomes, not guesses about a specific failure." I could have tried to diagnose the real cause from the last summary text. I didn't, on purpose: a runbook that hallucinates a root cause is worse than one that admits it only knows the outcome. And the bottom line of every page is a promise: "Runbook generation never retries or changes the task." Reading the runbook, even node chat.mjs runbook TASK_ID, can never mutate, re-run, or disable anything. Recovery text is advice, not an automatic retry. The guide went through eight focused verification checks, including a live desktop run where state and receipt shared one completion timestamp (distinctRuns=1 displayedOutcomes=1) — because the first draft showed a run twice, and a runbook that can't count its own runs is embarrassing. Ankita is an open-source, MIT-licensed personal AI companion that lives on your desktop — a little companion, a lot more possible. The code is in the repo linked above. Here's my design question: recovery text keyed to six generic outcomes is deliberately shallow — it tells you what class of failure happened and what to check first, but never attempts the actual diagnosis. Is that honest engineering (the page only claims what the scheduler can prove), or is it abdicating the real job a runbook is supposed to do — telling you what's actually wrong? Would you add cause-specific diagnosis, or does that path inevitably end in confident-sounding guesses?
Key Takeaways
- •How I built one-page runbooks for my AI companion's scheduled jobs My companion, Ankita, runs scheduled jobs while I'm away — a morning briefing, page watches, periodic digests
- •This story was reported by Dev.to, covering developments in the dev space.
- •AI advancements continue to reshape industries — read the full article on Dev.to for complete coverage.
📖 Continue reading the full article:
Read Full Article on Dev.to →


