All articles
Developers 12 min readThe Burrak AI Team

Your Agent Didn't Crash. It Just Never Told You.

Scheduled AI agents fail quietly. Six ways an unattended run goes wrong — never starting, running twice, timing out, finishing wrong — and what catches each.

On Monday at nine an agent was supposed to send you a competitor digest. It is now Thursday, and you only notice because a colleague asks what the digest said.

Nothing crashed. No error appeared anywhere you would look. The job simply did not happen — or happened and went nowhere — and the system's silence looked exactly like the system working.

This is the part of AI agents that demos skip. An agent in a chat window fails in front of you: you watch it wander, you read the bad answer, you type a correction. An agent on a schedule fails while you are asleep, and the default shape of that failure is silence. Most of the engineering in an unattended agent is not the model. It is everything that makes a run nobody watched either succeed or say so.

We run scheduled and background agents for our users, and at one point or another we have shipped most of the failures below. Here are the six ways an unattended run goes wrong, what each looks like from the outside, and what it takes to catch it.


WHAT THE SYSTEM HAS TO DO Claim fund it, lease once Run answer by deadline Verify judge vs the brief Record status + transcript Notify on change only WHAT GOES WRONG WITHOUT IT never starts, or starts twice stops mid-task with nothing wrong, but marked ok no way to see what it did silence, or sixteen alerts
Every stage of an unattended run has a failure that looks, from the outside, exactly like nothing happening.

1. It Never Started

The most common unattended failure is also the least visible: the run that never begins. There is no transcript, no error, no half-finished file. There is nothing to find.

For a while, ours had exactly this bug. The scheduler only picked up tasks whose agent was switched on. The rule was meant as a billing guard — don't run work on an agent nobody is paying to keep up — but its effect was that stopping an agent quietly stopped every task on it. The tasks stayed on the schedule, looking healthy, and never fired. Nothing recorded why, because from the scheduler's point of view nothing had happened.

The fix was not a better check. It was changing what the check does when it fails. The gate today is whether the account can pay for the run, not whether the agent happens to be on. When a due task is picked up, the agent time it needs is paid for first. If the account can't cover it, the task is not left sitting on the schedule, retrying invisibly every minute. It is paused, with the reason written on it, and the owner gets one email listing the tasks that stopped.

The general rule: a task that cannot run should change state where a person will see it. "Scheduled" has to mean "will run". A gate that turns into a silent skip is worse than no gate at all, because it makes the dashboard lie.

2. It Ran Twice

The opposite failure is quieter than you'd expect, and costlier. A report goes out twice. A reminder is sent twice. A row is written twice.

Double runs live in the gap between "this task is due" and "this task has run". If a scheduler checks every minute and a run takes five, a naive design sees the same task as due on each of those ticks. We close the gap at the moment of pickup: the next run time moves forward when the run is claimed, not when it finishes, and the claim is a single guarded database update that only one worker can win. A slow run can't be picked up again by the next tick, and two workers can't both take the same one.

What that cannot promise is exactly-once. If the machine running a task dies mid-run, the task would otherwise sit in "running" forever, so after a timeout it is handed back to the schedule and runs again. That is the right trade — a lost run is silent, a repeated one is at least visible — but it moves a responsibility onto whoever writes the brief.

So write briefs that are safe to repeat. "Append this week's numbers to the sheet" repeats badly. "Make sure this week's row exists and is correct" repeats fine. Anything that writes to a real system should look before it writes. For databases, it is the case for handing an agent a throwaway branch rather than production — the argument our Neon integration page makes at length.

3. It Ran Out of Time

Every agent loop needs a stopping rule, and the obvious one is a step limit: at most N model calls, then stop. It is simple, and in an unattended system it produces the worst available outcome — a run that did most of the work and returns none of it.

We learned this from our own configuration. Our cloud agents' production config carried a cap of eight steps, set months earlier with no recorded reason. Eight is plenty for "summarise this page". It is nowhere near enough for "research these five competitors and compare their pricing", which needs a search, several fetches and some reading per competitor. Those runs hit the ceiling, and the ceiling ended them with an error — limit reached without a final answer — and everything the agent had gathered was thrown away.

Two things changed:

  • The limit is time, not steps. A run gets a fifteen-minute budget. A step count is a poor proxy for work: one step can be a quick lookup or a long page read.
  • The budget ends in an answer, not an error. With two minutes left, the agent stops calling tools and is told to answer with what it has. Tool calls still in flight are held to the same deadline, so one slow fetch can't eat the reserve. A partial answer that says what it covered and what it didn't is worth something. An error message is not.

The same thinking applies to chat. In our web app, if you start a long job and close the tab, the run no longer depends on the connection that started it. It carries on in the background, inside the same budget, and the answer lands in that conversation's thread — where the notification rules in section 6 take over.

4. It Finished Wrong

Here is the failure that no amount of plumbing reaches: the run completes, returns confident text, and the text is wrong. The digest covers last month. The summary misses the one clause that mattered. The script "fixed" the bug by deleting the test.

In most schedulers, including our first version, that counts as a success. Success meant "no exception was raised." A task that confidently produced the wrong thing was recorded as fine, shown with a green tick, and never retried.

The fix is to separate finished from correct, and to have something other than the author judge the second. When verification is switched on for a task, each result goes to a separate judging call that sees the brief and the answer and must return a pass or a fail, with a reason. A fail triggers a retry that carries the failed answer and the critique, so the next attempt knows what was wrong instead of rolling the dice again. Retries are bounded twice over: by attempts (three at most) and by a wall-clock budget.

Three choices in there are easy to get wrong:

  • If the judge itself breaks, the run is marked unverified — not failed, and not retried. A flaky verifier must never turn a good answer into a loop of paid retries.
  • If every attempt fails, the best answer is still delivered, flagged as unverified. Suppressing it hides work you might still use, and makes a bad week look like an empty one.
  • The verdict is stored apart from the run status. "It ran" and "it passed the check" are different facts. Merging them brings back the green tick that lied.

Be honest about the limit, too. A verifier is a model judging a model. It catches the answer that ignored half the brief or covered the wrong date range. It will not catch a subtle factual error that both calls believe. It also costs a second model call on every run, which is why it is a per-task switch rather than a default.

5. You Can't See What It Did

When a chat goes wrong you scroll up. When an unattended run goes wrong, there is no scrollback unless the system kept one.

The final output alone is not enough to debug a run. "The competitor digest was thin" could mean the search returned nothing, a site blocked the fetch, the model skipped a step, or the brief was vague. Each has a different fix, and only the path the agent took tells you which.

So every run keeps its transcript: the prompt it was given, each tool it called and what came back, and the answer. Open any run of any task and read what happened, turn by turn. It is the most useful thing to have when a scheduled job disappoints, because it turns "the agent is bad at this" into "the second search had a typo in the company name".

6. It Finished, and Nobody Heard

The last failure looks like success on every internal dashboard. The run happened, the result was recorded, and the person it was for never found out.

We are not proud of this one. Until recently, a scheduled task that failed on our platform recorded the error on the task and did nothing else — no push, no message, no email. We assumed failures reached their owner. They didn't. Anyone who wasn't opening their task list found out the Thursday way.

Fixing it well took more care than "send a notification on failure", because naive alerting has its own failure mode. A task that runs every thirty minutes and starts failing at midnight produces sixteen alerts by breakfast, and by the third it is noise you have trained yourself to ignore. The rules we settled on:

  • Notify on a change, not on every event. The first failure of a streak posts a message into the task's own thread. The repeats don't, until the task succeeds and then fails again.
  • Push first; email only if it's still unread. The message arrives as a push notification. If it is still unread a couple of minutes later, it is emailed. Read it on your phone and the email never goes out.
  • Let a run choose silence. A monitoring task that checks something and finds it fine should be able to say nothing. A deliberately quiet run counts as a success and sends nothing — otherwise every hourly all-clear teaches you to ignore the channel that matters.
  • Make the notification a conversation. Reply to the email and the reply goes to the agent, which carries on in the same thread and sends its answer back. Because a reply can instruct an agent, it is only accepted when the sender's domain authenticates; a forged "From" line is refused, not obeyed.
  • Don't let a pause spend the alarm. A task paused because the account ran out of credits did not fail, so it doesn't use up the one message a real failure should get after you top up and resume.

A Checklist for Running Agents Unattended

Whatever runs your agents — us, a cron job and a script, or someone else's platform — these are the questions to ask before you leave a schedule running overnight:

  1. When a task can't run, does its visible state change? Or does "scheduled" keep saying scheduled?
  2. Can one run be picked up twice? And are your briefs safe if a crashed run repeats?
  3. Is the stopping rule a time budget that ends in an answer, or a step cap that ends in an error?
  4. Is "finished" separate from "correct"? Is there any check that isn't the author grading its own work?
  5. Can you read exactly what a run did, tool call by tool call, a week later?
  6. Does a failure reach you once — not never, and not sixteen times?
  7. Can a run with nothing to report stay quiet?

If the answer to any of these is no, the gap will not show up in a demo. It will show up on a Thursday.

What This Doesn't Solve

None of this makes an unattended agent safe to trust with everything. Specifically:

  • Verification is probabilistic. It raises the floor; it does not certify an answer. Review anything high-stakes before you act on it.
  • Idempotency is partly on you. The scheduler won't double-fire a healthy run, but a run lost to a crash will repeat. Write briefs that tolerate it.
  • One message per streak means one message. Ignore the first failure notice, and the task keeps failing quietly until it succeeds again.
  • Irreversible actions deserve a human. Moving money, deleting data, emailing customers in your name: keep those behind an approval step, or out of unattended tasks altogether. An agent that pauses to ask costs you a minute. One that doesn't can cost a great deal more.

The model is the interesting part of an AI agent. The unglamorous part — what picks up the run, what stops it, what checks it, and who hears about it — decides whether you can actually leave it alone.


Scheduling, verification and run transcripts are covered in the Tasks docs; how pushes, emails and replies reach you is in Notifications. If you are still deciding where an agent should run at all, start with local vs remote agents. And if your scheduled agent needs the same context re-explained every week, that is a memory problem rather than a scheduling one — here is why assistants forget.

In the docs

Read next