The cron run that started at 6:30 AM took 894.91 seconds, cost 735 credits, and ended with an EOF from the archive connector when it tried to deliver its findings. The daily notes never landed in the archive. The credits were gone anyway.
That was the daily agent on a credit-metered hosted platform. A scheduled task scans HN, a few subreddits, X and vendor blogs for the last 24 hours, picks 5 to 8 items, and drops one markdown file into our shared archive through a connector tool. At 735 credits a run, one day of it takes a big bite out of a prepaid balance that also has to cover everything else we hand that agent.
The run we kicked off anyway
That evening we were working through our partner’s latest update on our side venture. We trade updates through a shared git repo, and each of us has agents that read and write it. At 9:01 PM we pointed a task on the metered platform at the process question. It ran 145.6 seconds and burned 39 credits. The log records one word: (error).
A second, unmetered coding agent took up the same question at 9:02 PM. It answered in 25 seconds on 11,234 tokens. By 9:31 our own log line says the metered agent was out of credits and the second one had covered.
The wrong turn was treating the metered agent as the default and the others as the fallback. We learned the balance was empty because a job died, which is a bad way to read a balance. An error with no number attached looks the same as a flaky connector, and we had just watched that same connector throw an EOF that morning. We spent 39 credits finding out, on top of the 735 that bought nothing. We filed a ticket for an overdraft guard that checks the balance before dispatching, because nothing in the loop was doing that.
The daily job was a poll
With the two failures side by side, the daily job turned out to be a poll. Every morning a model wakes up, reads the internet, and burns credits whether or not anything changed.
What we needed most that night was to know the moment our partner pushed to the archive. The work moves by baton: our partner pushes, we read, we reply, the baton flips. Notifications ran through DMs from a shared platform bot. We built a dedicated Discord bot for the archive instead.
The shape of it:
- One channel per person, so our pushes and our partner’s pushes never share a feed.
- A check every 5 minutes against the archive repo.
- If the remote head differs from the last one it announced, post the new commit subjects and author to that person’s channel.
- No model anywhere in the loop.
The core is about this much shell:
git fetch -q originnew=$(git rev-parse origin/main)old=$(cat .last_posted 2>/dev/null)if [ "$new" != "$old" ]; then git log --format='%an: %s' "${old:-$new}..$new" | post_to_channel echo "$new" > .last_postedfiThat runs 288 times a day and costs zero tokens. It fires only when there is something to say. The daily agent is the opposite: it always fires and sometimes has something to say.
We verified the watcher end to end with a real push while the session was still open. The only loose end was that our partner still needed a server invite to pick up their side. Until they join, the channel for their pushes is a bot talking to us.
What the shutdown cost
We disabled the daily cron at 10:51 PM, so the news scan is off. Nothing replaced it. The scan’s top item that morning, a Claude Code release that stopped running its verify and code-review skills on its own, was a real find. It was sitting in a file that never reached the archive. If we want that job back, it will come back with a balance check in front of it.
The watcher handles the part of the job that was never a reasoning problem. “Did a file change in this repo” is a comparison of two hashes. We had been paying a model to poll for it, and later to fail at it, and the credit meter only told us when it was already too late.
Later that night we hit a different bug, a phone remote-control session that registered with its host service and died seconds later. Same reflex. When a job fails silently or with a bare error string, we no longer assume the model is the thing that has to fix it. First we ask whether the model needed to be in the loop at all.
Two checks for any scheduled agent
If an automated agent runs on a prepaid balance, make it check the balance before it dispatches work, so an empty balance does not show up as a bare error.
And for each scheduled AI job, ask whether it is only detecting a change, such as a new commit. If it is, a plain scripted check can do it for zero tokens and fire only when there is something to report.